{"thread":{"id":"60849","subject":"[Outreachy][PATCH 1/2] date: refactor 64 bit prereq code into reusable functions","startedAt":"2024-02-05T16:25:39Z","lastAt":"2024-06-27T06:49:37Z","messageCount":10,"participants":["Achu Luma","rsbecker@nexbridge.com","Christian Couder","Ghanshyam Thakkar","Junio C Hamano","Patrick Steinhardt","Johannes Schindelin"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"487935","messageId":"20240205162506.1835-1-ach.lumap@gmail.com","threadId":"60849","inReplyTo":null,"subject":"[Outreachy][PATCH 1/2] date: refactor 64 bit prereq code into reusable functions","fromName":"Achu Luma","fromEmail":"ach.lumap@gmail.com","sentAt":"2024-02-05T16:25:05Z","receivedAt":"2024-02-05T16:25:39Z","isPatch":true,"sender":{"key":"ach.lumap@gmail.com","avatar":"https://avatars.githubusercontent.com/u/142904668?v=4"},"body":"In a following commit we are going to port code from\n\"t/helper/test-date.c\" and \"t/t0006-date.sh\" to a new\n\"t/unit-tests/t-date.c\" file using the recently added unit test\nframework.\n\nWe cannot fully port all the code from \"t/helper/test-date.c\" though, as\nthe test-tool date helper is still used by a number of \"t/*.sh\" tests.\nThe TIME_IS_64BIT and TIME_T_IS_64BIT prereqs are especially used by\n\"t5000-tar-tree.sh\", \"t5318-commit-graph.sh\" and\n\"t5328-commit-graph-64bit-time.sh\" while checking those prereqs will be\nrequired in the new \"t/unit-tests/t-date.c\" file too.\n\nTo avoid duplicating in both \"t/helper/test-date.c\" and\n\"t/unit-tests/t-date.c\" the small amount of code checking these prereqs,\nlet's move it into inline functions in \"date.h\".\n\nThe names of these new inline functions contain \"TIME_IS_64BIT\" or\n\"TIME_T_IS_64BIT\" as it will simplify the macros we will use when\nwe will port code to \"t/unit-tests/t-date.c\" in a following commit.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Achu Luma <ach.lumap@gmail.com>\n---\n date.h               | 6 ++++++\n t/helper/test-date.c | 4 ++--\n 2 files changed, 8 insertions(+), 2 deletions(-)\n\ndiff --git a/date.h b/date.h\nindex 6136212a19..fb70490a51 100644\n--- a/date.h\n+++ b/date.h\n@@ -70,4 +70,10 @@ void datestamp(struct strbuf *out);\n timestamp_t approxidate_careful(const char *, int *);\n int date_overflows(timestamp_t date);\n time_t tm_to_time_t(const struct tm *tm);\n+static inline int check_prereq_TIME_IS_64BIT(void) {\n+\treturn sizeof(timestamp_t) == 8;\n+}\n+static inline int check_prereq_TIME_T_IS_64BIT(void) {\n+\treturn sizeof(time_t) == 8;\n+}\n #endif\ndiff --git a/t/helper/test-date.c b/t/helper/test-date.c\nindex 0683d46574..be0b8679c3 100644\n--- a/t/helper/test-date.c\n+++ b/t/helper/test-date.c\n@@ -126,9 +126,9 @@ int cmd__date(int argc UNUSED, const char **argv)\n \telse if (!strcmp(*argv, \"getnanos\"))\n \t\tgetnanos(argv+1);\n \telse if (!strcmp(*argv, \"is64bit\"))\n-\t\treturn sizeof(timestamp_t) == 8 ? 0 : 1;\n+\t\treturn !check_prereq_TIME_IS_64BIT();\n \telse if (!strcmp(*argv, \"time_t-is64bit\"))\n-\t\treturn sizeof(time_t) == 8 ? 0 : 1;\n+\t\treturn !check_prereq_TIME_T_IS_64BIT();\n \telse\n \t\tusage(usage_msg);\n \treturn 0;\n--\n2.43.0.windows.1\n\n"},{"id":"487936","messageId":"20240205162506.1835-2-ach.lumap@gmail.com","threadId":"60849","inReplyTo":"20240205162506.1835-1-ach.lumap@gmail.com","subject":"[Outreachy][PATCH 2/2] Port helper/test-date.c to unit-tests/t-date.c","fromName":"Achu Luma","fromEmail":"ach.lumap@gmail.com","sentAt":"2024-02-05T16:25:06Z","receivedAt":"2024-02-05T16:25:50Z","isPatch":true,"sender":{"key":"ach.lumap@gmail.com","avatar":"https://avatars.githubusercontent.com/u/142904668?v=4"},"body":"In the recent codebase update (8bf6fbd (Merge branch\n'js/doc-unit-tests', 2023-12-09)), a new unit testing framework was\nmerged, providing a standardized approach for testing C code. Prior to\nthis update, some unit tests relied on the test helper mechanism,\nlacking a dedicated unit testing framework. It's more natural to perform\nthese unit tests using the new unit test framework.\n\nThis commit migrates the unit tests for C date functions\n(show_date_relative(), show_date(), etc) from the legacy approach\nusing the test-tool command `test-tool date` in t/helper/test-date.c\nto the new unit testing framework (t/unit-tests/test-lib.h).\n\nThe migration involves refactoring the tests to utilize the testing\nmacros provided by the framework (TEST() and check_*()).\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Achu Luma <ach.lumap@gmail.com>\n---\n I am currently facing challenges in handling the time zone (TZ)\n variable, in t-date.c. The tests, which were initially passing\n in the t0006-date.sh  on Windows, fail after porting to the new\n framework. I have tried to set the timezone using the setenv,\n but unfortunately, the issue persists. I suspect that the problem\n might be related to how Windows C compilers process the setenv\n function compared to POSIX environments. Please If anyone has\n insights into managing environment variables in a cross-platform\n manner or has encountered similar issues, your help would be\n highly appreciated.\n\n t/helper/test-date.c  |  91 +---------------\n t/t0006-date.sh       | 169 ------------------------------\n t/unit-tests/t-date.c | 237 ++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 238 insertions(+), 259 deletions(-)\n delete mode 100755 t/t0006-date.sh\n create mode 100644 t/unit-tests/t-date.c\n\ndiff --git a/t/helper/test-date.c b/t/helper/test-date.c\nindex be0b8679c3..b9cb2c5455 100644\n--- a/t/helper/test-date.c\n+++ b/t/helper/test-date.c\n@@ -3,89 +3,11 @@\n #include \"trace.h\"\n\n static const char *usage_msg = \"\\n\"\n-\"  test-tool date relative [time_t]...\\n\"\n-\"  test-tool date human [time_t]...\\n\"\n-\"  test-tool date show:<format> [time_t]...\\n\"\n-\"  test-tool date parse [date]...\\n\"\n-\"  test-tool date approxidate [date]...\\n\"\n \"  test-tool date timestamp [date]...\\n\"\n \"  test-tool date getnanos [start-nanos]\\n\"\n \"  test-tool date is64bit\\n\"\n \"  test-tool date time_t-is64bit\\n\";\n\n-static void show_relative_dates(const char **argv)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tfor (; *argv; argv++) {\n-\t\ttime_t t = atoi(*argv);\n-\t\tshow_date_relative(t, &buf);\n-\t\tprintf(\"%s -> %s\\n\", *argv, buf.buf);\n-\t}\n-\tstrbuf_release(&buf);\n-}\n-\n-static void show_human_dates(const char **argv)\n-{\n-\tfor (; *argv; argv++) {\n-\t\ttime_t t = atoi(*argv);\n-\t\tprintf(\"%s -> %s\\n\", *argv, show_date(t, 0, DATE_MODE(HUMAN)));\n-\t}\n-}\n-\n-static void show_dates(const char **argv, const char *format)\n-{\n-\tstruct date_mode mode = DATE_MODE_INIT;\n-\n-\tparse_date_format(format, &mode);\n-\tfor (; *argv; argv++) {\n-\t\tchar *arg;\n-\t\ttimestamp_t t;\n-\t\tint tz;\n-\n-\t\t/*\n-\t\t * Do not use our normal timestamp parsing here, as the point\n-\t\t * is to test the formatting code in isolation.\n-\t\t */\n-\t\tt = parse_timestamp(*argv, &arg, 10);\n-\t\twhile (*arg == ' ')\n-\t\t\targ++;\n-\t\ttz = atoi(arg);\n-\n-\t\tprintf(\"%s -> %s\\n\", *argv, show_date(t, tz, &mode));\n-\t}\n-\n-\tdate_mode_release(&mode);\n-}\n-\n-static void parse_dates(const char **argv)\n-{\n-\tstruct strbuf result = STRBUF_INIT;\n-\n-\tfor (; *argv; argv++) {\n-\t\ttimestamp_t t;\n-\t\tint tz;\n-\n-\t\tstrbuf_reset(&result);\n-\t\tparse_date(*argv, &result);\n-\t\tif (sscanf(result.buf, \"%\"PRItime\" %d\", &t, &tz) == 2)\n-\t\t\tprintf(\"%s -> %s\\n\",\n-\t\t\t       *argv, show_date(t, tz, DATE_MODE(ISO8601)));\n-\t\telse\n-\t\t\tprintf(\"%s -> bad\\n\", *argv);\n-\t}\n-\tstrbuf_release(&result);\n-}\n-\n-static void parse_approxidate(const char **argv)\n-{\n-\tfor (; *argv; argv++) {\n-\t\ttimestamp_t t;\n-\t\tt = approxidate(*argv);\n-\t\tprintf(\"%s -> %s\\n\", *argv, show_date(t, 0, DATE_MODE(ISO8601)));\n-\t}\n-}\n-\n static void parse_approx_timestamp(const char **argv)\n {\n \tfor (; *argv; argv++) {\n@@ -106,22 +28,11 @@ static void getnanos(const char **argv)\n\n int cmd__date(int argc UNUSED, const char **argv)\n {\n-\tconst char *x;\n\n \targv++;\n \tif (!*argv)\n \t\tusage(usage_msg);\n-\tif (!strcmp(*argv, \"relative\"))\n-\t\tshow_relative_dates(argv+1);\n-\telse if (!strcmp(*argv, \"human\"))\n-\t\tshow_human_dates(argv+1);\n-\telse if (skip_prefix(*argv, \"show:\", &x))\n-\t\tshow_dates(argv+1, x);\n-\telse if (!strcmp(*argv, \"parse\"))\n-\t\tparse_dates(argv+1);\n-\telse if (!strcmp(*argv, \"approxidate\"))\n-\t\tparse_approxidate(argv+1);\n-\telse if (!strcmp(*argv, \"timestamp\"))\n+\tif (!strcmp(*argv, \"timestamp\"))\n \t\tparse_approx_timestamp(argv+1);\n \telse if (!strcmp(*argv, \"getnanos\"))\n \t\tgetnanos(argv+1);\ndiff --git a/t/t0006-date.sh b/t/t0006-date.sh\ndeleted file mode 100755\nindex e18b160286..0000000000\n--- a/t/t0006-date.sh\n+++ /dev/null\n@@ -1,169 +0,0 @@\n-#!/bin/sh\n-\n-test_description='test date parsing and printing'\n-\n-TEST_PASSES_SANITIZE_LEAK=true\n-. ./test-lib.sh\n-\n-# arbitrary reference time: 2009-08-30 19:20:00\n-GIT_TEST_DATE_NOW=1251660000; export GIT_TEST_DATE_NOW\n-\n-check_relative() {\n-\tt=$(($GIT_TEST_DATE_NOW - $1))\n-\techo \"$t -> $2\" >expect\n-\ttest_expect_${3:-success} \"relative date ($2)\" \"\n-\ttest-tool date relative $t >actual &&\n-\ttest_cmp expect actual\n-\t\"\n-}\n-\n-check_relative 5 '5 seconds ago'\n-check_relative 300 '5 minutes ago'\n-check_relative 18000 '5 hours ago'\n-check_relative 432000 '5 days ago'\n-check_relative 1728000 '3 weeks ago'\n-check_relative 13000000 '5 months ago'\n-check_relative 37500000 '1 year, 2 months ago'\n-check_relative 55188000 '1 year, 9 months ago'\n-check_relative 630000000 '20 years ago'\n-check_relative 31449600 '12 months ago'\n-check_relative 62985600 '2 years ago'\n-\n-check_show () {\n-\tformat=$1\n-\ttime=$2\n-\texpect=$3\n-\tprereqs=$4\n-\tzone=$5\n-\ttest_expect_success $prereqs \"show date ($format:$time)\" '\n-\t\techo \"$time -> $expect\" >expect &&\n-\t\tTZ=${zone:-$TZ} test-tool date show:\"$format\" \"$time\" >actual &&\n-\t\ttest_cmp expect actual\n-\t'\n-}\n-\n-# arbitrary but sensible time for examples\n-TIME='1466000000 +0200'\n-check_show iso8601 \"$TIME\" '2016-06-15 16:13:20 +0200'\n-check_show iso8601-strict \"$TIME\" '2016-06-15T16:13:20+02:00'\n-check_show rfc2822 \"$TIME\" 'Wed, 15 Jun 2016 16:13:20 +0200'\n-check_show short \"$TIME\" '2016-06-15'\n-check_show default \"$TIME\" 'Wed Jun 15 16:13:20 2016 +0200'\n-check_show raw \"$TIME\" '1466000000 +0200'\n-check_show unix \"$TIME\" '1466000000'\n-check_show iso-local \"$TIME\" '2016-06-15 14:13:20 +0000'\n-check_show raw-local \"$TIME\" '1466000000 +0000'\n-check_show unix-local \"$TIME\" '1466000000'\n-\n-check_show 'format:%z' \"$TIME\" '+0200'\n-check_show 'format-local:%z' \"$TIME\" '+0000'\n-check_show 'format:%Z' \"$TIME\" ''\n-check_show 'format-local:%Z' \"$TIME\" 'UTC'\n-check_show 'format:%%z' \"$TIME\" '%z'\n-check_show 'format-local:%%z' \"$TIME\" '%z'\n-\n-check_show 'format:%Y-%m-%d %H:%M:%S' \"$TIME\" '2016-06-15 16:13:20'\n-check_show 'format-local:%Y-%m-%d %H:%M:%S' \"$TIME\" '2016-06-15 09:13:20' '' EST5\n-\n-check_show 'format:%s' '123456789 +1234' 123456789\n-check_show 'format:%s' '123456789 -1234' 123456789\n-check_show 'format-local:%s' '123456789 -1234' 123456789\n-\n-# arbitrary time absurdly far in the future\n-FUTURE=\"5758122296 -0400\"\n-check_show iso       \"$FUTURE\" \"2152-06-19 18:24:56 -0400\" TIME_IS_64BIT,TIME_T_IS_64BIT\n-check_show iso-local \"$FUTURE\" \"2152-06-19 22:24:56 +0000\" TIME_IS_64BIT,TIME_T_IS_64BIT\n-\n-check_parse() {\n-\techo \"$1 -> $2\" >expect\n-\ttest_expect_${4:-success} \"parse date ($1${3:+ TZ=$3})\" \"\n-\tTZ=${3:-$TZ} test-tool date parse '$1' >actual &&\n-\ttest_cmp expect actual\n-\t\"\n-}\n-\n-check_parse 2008 bad\n-check_parse 2008-02 bad\n-check_parse 2008-02-14 bad\n-check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 +0000'\n-check_parse '2008-02-14 20:30:45 -0500' '2008-02-14 20:30:45 -0500'\n-check_parse '2008.02.14 20:30:45 -0500' '2008-02-14 20:30:45 -0500'\n-check_parse '20080214T20:30:45' '2008-02-14 20:30:45 +0000'\n-check_parse '20080214T20:30' '2008-02-14 20:30:00 +0000'\n-check_parse '20080214T20' '2008-02-14 20:00:00 +0000'\n-check_parse '20080214T203045' '2008-02-14 20:30:45 +0000'\n-check_parse '20080214T2030' '2008-02-14 20:30:00 +0000'\n-check_parse '20080214T000000.20' '2008-02-14 00:00:00 +0000'\n-check_parse '20080214T00:00:00.20' '2008-02-14 00:00:00 +0000'\n-check_parse '20080214T203045-04:00' '2008-02-14 20:30:45 -0400'\n-check_parse '20080214T203045 -04:00' '2008-02-14 20:30:45 -0400'\n-check_parse '20080214T203045.019-04:00' '2008-02-14 20:30:45 -0400'\n-check_parse '2008-02-14 20:30:45.019-04:00' '2008-02-14 20:30:45 -0400'\n-check_parse '2008-02-14 20:30:45 -0015' '2008-02-14 20:30:45 -0015'\n-check_parse '2008-02-14 20:30:45 -5' '2008-02-14 20:30:45 +0000'\n-check_parse '2008-02-14 20:30:45 -5:' '2008-02-14 20:30:45 +0000'\n-check_parse '2008-02-14 20:30:45 -05' '2008-02-14 20:30:45 -0500'\n-check_parse '2008-02-14 20:30:45 -:30' '2008-02-14 20:30:45 +0000'\n-check_parse '2008-02-14 20:30:45 -05:00' '2008-02-14 20:30:45 -0500'\n-check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 -0500' EST5\n-check_parse 'Thu, 7 Apr 2005 15:14:13 -0700' '2005-04-07 15:14:13 -0700'\n-\n-check_approxidate() {\n-\techo \"$1 -> $2 +0000\" >expect\n-\ttest_expect_${3:-success} \"parse approxidate ($1)\" \"\n-\ttest-tool date approxidate '$1' >actual &&\n-\ttest_cmp expect actual\n-\t\"\n-}\n-\n-check_approxidate now '2009-08-30 19:20:00'\n-check_approxidate '5 seconds ago' '2009-08-30 19:19:55'\n-check_approxidate 5.seconds.ago '2009-08-30 19:19:55'\n-check_approxidate 10.minutes.ago '2009-08-30 19:10:00'\n-check_approxidate yesterday '2009-08-29 19:20:00'\n-check_approxidate 3.days.ago '2009-08-27 19:20:00'\n-check_approxidate '12:34:56.3.days.ago' '2009-08-27 12:34:56'\n-check_approxidate 3.weeks.ago '2009-08-09 19:20:00'\n-check_approxidate 3.months.ago '2009-05-30 19:20:00'\n-check_approxidate 2.years.3.months.ago '2007-05-30 19:20:00'\n-\n-check_approxidate '6am yesterday' '2009-08-29 06:00:00'\n-check_approxidate '6pm yesterday' '2009-08-29 18:00:00'\n-check_approxidate '3:00' '2009-08-30 03:00:00'\n-check_approxidate '15:00' '2009-08-30 15:00:00'\n-check_approxidate 'noon today' '2009-08-30 12:00:00'\n-check_approxidate 'noon yesterday' '2009-08-29 12:00:00'\n-check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00'\n-check_approxidate '10am noon' '2009-08-29 12:00:00'\n-\n-check_approxidate 'last tuesday' '2009-08-25 19:20:00'\n-check_approxidate 'July 5th' '2009-07-05 19:20:00'\n-check_approxidate '06/05/2009' '2009-06-05 19:20:00'\n-check_approxidate '06.05.2009' '2009-05-06 19:20:00'\n-\n-check_approxidate 'Jun 6, 5AM' '2009-06-06 05:00:00'\n-check_approxidate '5AM Jun 6' '2009-06-06 05:00:00'\n-check_approxidate '6AM, June 7, 2009' '2009-06-07 06:00:00'\n-\n-check_approxidate '2008-12-01' '2008-12-01 19:20:00'\n-check_approxidate '2009-12-01' '2009-12-01 19:20:00'\n-\n-check_date_format_human() {\n-\tt=$(($GIT_TEST_DATE_NOW - $1))\n-\techo \"$t -> $2\" >expect\n-\ttest_expect_success \"human date $t\" '\n-\t\ttest-tool date human $t >actual &&\n-\t\ttest_cmp expect actual\n-'\n-}\n-\n-check_date_format_human 18000 \"5 hours ago\" # 5 hours ago\n-check_date_format_human 432000 \"Tue Aug 25 19:20\" # 5 days ago\n-check_date_format_human 1728000 \"Mon Aug 10 19:20\" # 3 weeks ago\n-check_date_format_human 13000000 \"Thu Apr 2 08:13\" # 5 months ago\n-check_date_format_human 31449600 \"Aug 31 2008\" # 12 months ago\n-check_date_format_human 37500000 \"Jun 22 2008\" # 1 year, 2 months ago\n-check_date_format_human 55188000 \"Dec 1 2007\" # 1 year, 9 months ago\n-check_date_format_human 630000000 \"Sep 13 1989\" # 20 years ago\n-\n-test_done\ndiff --git a/t/unit-tests/t-date.c b/t/unit-tests/t-date.c\nnew file mode 100644\nindex 0000000000..dd5dbbb2e0\n--- /dev/null\n+++ b/t/unit-tests/t-date.c\n@@ -0,0 +1,237 @@\n+#include \"test-lib.h\"\n+#include \"date.h\"\n+#include \"strbuf.h\"\n+\n+/* Reference time: 2009-08-30 19:20:00. */\n+#define GIT_TEST_DATE_NOW 1251660000\n+\n+/* The time corresponds to Wed, 15 Jun 2016 16:13:20 +0200. */\n+static const char test_time[] = \"1466000000 +0200\";\n+\n+enum prerequisites {\n+    \tTIME_IS_64BIT = 1 << 0,\n+    \tTIME_T_IS_64BIT = 1 << 1,\n+};\n+\n+/* Macro to check prerequisites */\n+#define CHECK_PREREQ(var, prereq) \\\n+    \tdo { \\\n+\t\tif ((var) & prereq && !check_prereq_##prereq()) { \\\n+\t\t\ttest_skip(\"missing prerequisite \" #prereq); \\\n+\t\t\treturn 0; \\\n+\t\t} \\\n+\t} while (0)\n+\n+/* Return 1 if all prereqs are satisfied, 0 otherwise */\n+static int check_prereqs(unsigned int prereqs) {\n+    \tCHECK_PREREQ(prereqs, TIME_IS_64BIT);\n+    \tCHECK_PREREQ(prereqs, TIME_T_IS_64BIT);\n+\n+    \treturn 1;\n+}\n+\n+static void set_TZ_env(const char *zone) {\n+\tsetenv(\"TZ\", zone, 1);\n+\ttzset();\n+}\n+\n+static void check_relative_dates(int time_val, const char *expected_date) {\n+\tstruct strbuf buf = STRBUF_INIT;\n+\ttimestamp_t diff = GIT_TEST_DATE_NOW - time_val;\n+\n+\tshow_date_relative(diff, &buf);\n+\tcheck_str(buf.buf, expected_date);\n+\tstrbuf_release(&buf);\n+}\n+\n+#define TEST_RELATIVE_DATE(value, expected_output) \\\n+    \tTEST(check_relative_dates(value, expected_output), \\\n+        \t\"relative date (%s) works\", #expected_output )\n+\n+static void check_show_date(const char *format, const char *TIME, const char *expected, unsigned int prereqs, const char *zone) {\n+\tstruct date_mode mode = DATE_MODE_INIT;\n+\tchar *arg;\n+\ttimestamp_t t;\n+\tint tz;\n+\n+\tif (!check_prereqs(prereqs))\n+\t\treturn;\n+\tif (strcmp(zone, \"\"))\n+\t\tset_TZ_env(zone);\n+\n+\tparse_date_format(format, &mode);\n+\tt = parse_timestamp(TIME, &arg, 10);\n+\ttz = atoi(arg);\n+\n+\tcheck_str(show_date(t, tz, &mode), expected);\n+\n+\tif (strcmp(zone, \"\"))\n+\t\tset_TZ_env(\"UTC\");\n+\tdate_mode_release(&mode);\n+}\n+\n+#define TEST_SHOW_DATE(format, time, expected, prereqs, zone) \\\n+\tTEST(check_show_date(format, time, expected, prereqs, zone), \\\n+\t     \"show date (%s) works\", #format)\n+\n+static void check_parse_date(const char *given, const char *expected, const char *zone) {\n+\tstruct strbuf result = STRBUF_INIT;\n+\ttimestamp_t t;\n+\tint tz;\n+\n+\tif (strcmp(zone, \"\"))\n+\t\tset_TZ_env(zone);\n+\n+\tparse_date(given, &result);\n+\tif (sscanf(result.buf, \"%\"PRItime\" %d\", &t, &tz) == 2)\n+\t\tcheck_str(show_date(t, tz, DATE_MODE(ISO8601)), expected);\n+\telse\n+\t\tcheck_str(\"bad\", expected);\n+\n+\tif (strcmp(zone, \"\"))\n+\t\tset_TZ_env(\"UTC\");\n+\tstrbuf_release(&result);\n+}\n+\n+#define TEST_PARSE_DATE(given, expected_output, zone) \\\n+    \tTEST(check_parse_date(given, expected_output, zone), \\\n+        \t\"parse date (%s) works\", #expected_output)\n+\n+static void check_approxidate(const char *given, const char *expected) {\n+\ttimestamp_t t = approxidate(given);\n+\tchar *expected_with_offset = xstrfmt(\"%s +0000\", expected);\n+\n+\tcheck_str(show_date(t, 0, DATE_MODE(ISO8601)), expected_with_offset);\n+\tfree(expected_with_offset);\n+}\n+\n+#define TEST_APPROXIDATE(given, expected_output) \\\n+    \tTEST(check_approxidate(given, expected_output), \\\n+        \t\"parse approxidate (%s) works\", #given)\n+\n+static void check_date_format_human(int given, const char *expected) {\n+\ttimestamp_t diff = GIT_TEST_DATE_NOW - given;\n+\tcheck_str(show_date(diff, 0, DATE_MODE(HUMAN)), expected);\n+}\n+\n+#define TEST_DATE_FORMAT_HUMAN(given, expected_output) \\\n+    \tTEST(check_date_format_human(given, expected_output), \\\n+        \t\"human date (%s) works\", #given)\n+\n+int cmd_main(int argc, const char **argv) {\n+\tset_TZ_env(\"UTC\");\n+\tsetenv(\"GIT_TEST_DATE_NOW\", \"1251660000\", 1);\n+\tsetenv(\"LANG\", \"C\", 1);\n+\n+\tTEST_RELATIVE_DATE(5, \"5 seconds ago\");\n+\tTEST_RELATIVE_DATE(300, \"5 minutes ago\");\n+\tTEST_RELATIVE_DATE(18000, \"5 hours ago\");\n+\tTEST_RELATIVE_DATE(432000, \"5 days ago\");\n+\tTEST_RELATIVE_DATE(1728000, \"3 weeks ago\");\n+\tTEST_RELATIVE_DATE(13000000, \"5 months ago\");\n+\tTEST_RELATIVE_DATE(37500000, \"1 year, 2 months ago\");\n+\tTEST_RELATIVE_DATE(55188000, \"1 year, 9 months ago\");\n+\tTEST_RELATIVE_DATE(630000000, \"20 years ago\");\n+\tTEST_RELATIVE_DATE(31449600, \"12 months ago\");\n+\tTEST_RELATIVE_DATE(62985600, \"2 years ago\");\n+\n+\tTEST_SHOW_DATE(\"iso8601\", test_time, \"2016-06-15 16:13:20 +0200\", 0, \"\");\n+\tTEST_SHOW_DATE(\"iso8601-strict\", test_time, \"2016-06-15T16:13:20+02:00\", 0, \"\");\n+\tTEST_SHOW_DATE(\"rfc2822\", test_time, \"Wed, 15 Jun 2016 16:13:20 +0200\", 0, \"\");\n+\tTEST_SHOW_DATE(\"short\", test_time, \"2016-06-15\", 0, \"\");\n+\tTEST_SHOW_DATE(\"default\", test_time, \"Wed Jun 15 16:13:20 2016 +0200\", 0, \"\");\n+\tTEST_SHOW_DATE(\"raw\", test_time, test_time, 0, \"\");\n+\tTEST_SHOW_DATE(\"unix\", test_time, \"1466000000\", 0, \"\");\n+\tTEST_SHOW_DATE(\"iso-local\", test_time, \"2016-06-15 14:13:20 +0000\", 0, \"\");\n+\tTEST_SHOW_DATE(\"raw-local\", test_time, \"1466000000 +0000\", 0, \"\");\n+\tTEST_SHOW_DATE(\"unix-local\", test_time, \"1466000000\", 0, \"\");\n+\n+\tTEST_SHOW_DATE(\"format:%z\", test_time, \"+0200\", 0, \"\");\n+\tTEST_SHOW_DATE(\"format-local:%z\", test_time, \"+0000\", 0, \"\");\n+\tTEST_SHOW_DATE(\"format:%Z\", test_time, \"\", 0, \"\");\n+\tTEST_SHOW_DATE(\"format-local:%Z\", test_time, \"UTC\", 0, \"\");\n+\tTEST_SHOW_DATE(\"format:%%z\", test_time, \"%z\", 0, \"\");\n+\tTEST_SHOW_DATE(\"format-local:%%z\", test_time, \"%z\", 0, \"\");\n+\n+\tTEST_SHOW_DATE(\"format:%Y-%m-%d %H:%M:%S\", test_time, \"2016-06-15 16:13:20\", 0, \"\");\n+\n+\tTEST_SHOW_DATE(\"format-local:%Y-%m-%d %H:%M:%S\", test_time, \"2016-06-15 09:13:20\", 0, \"EST5\");\n+\n+\tTEST_SHOW_DATE(\"format:%s\", \"123456789 +1234\", \"123456789\", 0, \"\");\n+\tTEST_SHOW_DATE(\"format:%s\", \"123456789 -1234\", \"123456789\", 0, \"\");\n+\tTEST_SHOW_DATE(\"format-local:%s\", \"123456789 -1234\", \"123456789\", 0, \"\");\n+\n+\t/* Arbitrary time absurdly far in the future */\n+\tTEST_SHOW_DATE(\"iso\", \"5758122296 -0400\", \"2152-06-19 18:24:56 -0400\", TIME_IS_64BIT | TIME_T_IS_64BIT, \"\");\n+\tTEST_SHOW_DATE(\"iso-local\", \"5758122296 -0400\", \"2152-06-19 22:24:56 +0000\", TIME_IS_64BIT | TIME_T_IS_64BIT, \"\");\n+\n+\tTEST_PARSE_DATE(\"2000\", \"bad\", \"\");\n+\tTEST_PARSE_DATE(\"2008-02\", \"bad\", \"\");\n+\tTEST_PARSE_DATE(\"2008-02-14\", \"bad\", \"\");\n+\tTEST_PARSE_DATE(\"2008-02-14 20:30:45\", \"2008-02-14 20:30:45 +0000\", \"\");\n+\tTEST_PARSE_DATE(\"2008-02-14 20:30:45 -0500\", \"2008-02-14 20:30:45 -0500\", \"\");\n+\tTEST_PARSE_DATE(\"2008.02.14 20:30:45 -0500\", \"2008-02-14 20:30:45 -0500\", \"\");\n+\tTEST_PARSE_DATE(\"20080214T20:30:45\", \"2008-02-14 20:30:45 +0000\", \"\");\n+\tTEST_PARSE_DATE(\"20080214T20:30\", \"2008-02-14 20:30:00 +0000\", \"\");\n+\tTEST_PARSE_DATE(\"20080214T20\", \"2008-02-14 20:00:00 +0000\", \"\");\n+\tTEST_PARSE_DATE(\"20080214T203045\", \"2008-02-14 20:30:45 +0000\", \"\");\n+\tTEST_PARSE_DATE(\"20080214T2030\", \"2008-02-14 20:30:00 +0000\", \"\");\n+\tTEST_PARSE_DATE(\"20080214T000000.20\", \"2008-02-14 00:00:00 +0000\", \"\");\n+\tTEST_PARSE_DATE(\"20080214T00:00:00.20\", \"2008-02-14 00:00:00 +0000\", \"\");\n+\tTEST_PARSE_DATE(\"20080214T203045-04:00\", \"2008-02-14 20:30:45 -0400\", \"\");\n+\n+\tTEST_PARSE_DATE(\"20080214T203045 -04:00\", \"2008-02-14 20:30:45 -0400\", \"\");\n+\tTEST_PARSE_DATE(\"20080214T203045.019-04:00\", \"2008-02-14 20:30:45 -0400\", \"\");\n+\tTEST_PARSE_DATE(\"2008-02-14 20:30:45.019-04:00\", \"2008-02-14 20:30:45 -0400\", \"\");\n+\tTEST_PARSE_DATE(\"2008-02-14 20:30:45 -0015\", \"2008-02-14 20:30:45 -0015\", \"\");\n+\tTEST_PARSE_DATE(\"2008-02-14 20:30:45 -5\", \"2008-02-14 20:30:45 +0000\", \"\");\n+\tTEST_PARSE_DATE(\"2008-02-14 20:30:45 -5:\", \"2008-02-14 20:30:45 +0000\", \"\");\n+\tTEST_PARSE_DATE(\"2008-02-14 20:30:45 -05\", \"2008-02-14 20:30:45 -0500\", \"\");\n+\tTEST_PARSE_DATE(\"2008-02-14 20:30:45 -:30\", \"2008-02-14 20:30:45 +0000\", \"\");\n+\tTEST_PARSE_DATE(\"2008-02-14 20:30:45 -05:00\", \"2008-02-14 20:30:45 -0500\", \"\");\n+\tTEST_PARSE_DATE(\"2008-02-14 20:30:45\", \"2008-02-14 20:30:45 -0500\", \"EST5\");\n+\n+\tTEST_PARSE_DATE(\"Thu, 7 Apr 2005 15:14:13 -0700\", \"2005-04-07 15:14:13 -0700\", \"\");\n+\n+\tTEST_APPROXIDATE(\"now\", \"2009-08-30 19:20:00\");\n+\tTEST_APPROXIDATE(\"5 seconds ago\", \"2009-08-30 19:19:55\");\n+\tTEST_APPROXIDATE(\"10 minutes ago\", \"2009-08-30 19:10:00\");\n+\tTEST_APPROXIDATE(\"yesterday\", \"2009-08-29 19:20:00\");\n+\tTEST_APPROXIDATE(\"3 days ago\", \"2009-08-27 19:20:00\");\n+\tTEST_APPROXIDATE(\"12:34:56.3 days ago\", \"2009-08-27 12:34:56\");\n+\tTEST_APPROXIDATE(\"3 weeks ago\", \"2009-08-09 19:20:00\");\n+\tTEST_APPROXIDATE(\"3 months ago\", \"2009-05-30 19:20:00\");\n+\tTEST_APPROXIDATE(\"2 years 3 months ago\", \"2007-05-30 19:20:00\");\n+\n+\tTEST_APPROXIDATE(\"6am yesterday\", \"2009-08-29 06:00:00\");\n+\tTEST_APPROXIDATE(\"6pm yesterday\", \"2009-08-29 18:00:00\");\n+\tTEST_APPROXIDATE(\"3:00\", \"2009-08-30 03:00:00\");\n+\tTEST_APPROXIDATE(\"15:00\", \"2009-08-30 15:00:00\");\n+\tTEST_APPROXIDATE(\"noon today\", \"2009-08-30 12:00:00\");\n+\tTEST_APPROXIDATE(\"noon yesterday\", \"2009-08-29 12:00:00\");\n+\tTEST_APPROXIDATE(\"January 5th noon pm\", \"2009-01-05 12:00:00\");\n+\tTEST_APPROXIDATE(\"10am noon\", \"2009-08-29 12:00:00\");\n+\n+\tTEST_APPROXIDATE(\"last tuesday\", \"2009-08-25 19:20:00\");\n+\tTEST_APPROXIDATE(\"July 5th\", \"2009-07-05 19:20:00\");\n+\tTEST_APPROXIDATE(\"06/05/2009\", \"2009-06-05 19:20:00\");\n+\tTEST_APPROXIDATE(\"06.05.2009\", \"2009-05-06 19:20:00\");\n+\n+\tTEST_APPROXIDATE(\"Jun 6, 5AM\", \"2009-06-06 05:00:00\");\n+\tTEST_APPROXIDATE(\"5AM Jun 6\", \"2009-06-06 05:00:00\");\n+\tTEST_APPROXIDATE(\"6AM, June 7, 2009\", \"2009-06-07 06:00:00\");\n+\n+\tTEST_APPROXIDATE(\"2008-12-01\", \"2008-12-01 19:20:00\");\n+\tTEST_APPROXIDATE(\"2009-12-01\", \"2009-12-01 19:20:00\");\n+\n+\tTEST_DATE_FORMAT_HUMAN(18000, \"5 hours ago\");\n+\tTEST_DATE_FORMAT_HUMAN(432000, \"Tue Aug 25 19:20\");\n+\tTEST_DATE_FORMAT_HUMAN(1728000, \"Mon Aug 10 19:20\");\n+\tTEST_DATE_FORMAT_HUMAN(13000000, \"Thu Apr 2 08:13\");\n+\tTEST_DATE_FORMAT_HUMAN(31449600, \"Aug 31 2008\");\n+\tTEST_DATE_FORMAT_HUMAN(37500000, \"Jun 22 2008\");\n+\tTEST_DATE_FORMAT_HUMAN(55188000, \"Dec 1 2007\");\n+\tTEST_DATE_FORMAT_HUMAN(630000000, \"Sep 13 1989\");\n+\n+\treturn test_done();\n+}\n--\n2.43.0.windows.1\n\n"},{"id":"487941","messageId":"020a01da5859$89e41210$9dac3630$@nexbridge.com","threadId":"60849","inReplyTo":"20240205162506.1835-1-ach.lumap@gmail.com","subject":"RE: [Outreachy][PATCH 1/2] date: refactor 64 bit prereq code into reusable functions","fromName":"","fromEmail":"rsbecker@nexbridge.com","sentAt":"2024-02-05T17:34:09Z","receivedAt":"2024-02-05T17:34:20Z","isPatch":true,"sender":{"key":"randall.becker@nexbridge.ca","avatar":"https://avatars.githubusercontent.com/u/28956764?v=4"},"body":"On Monday, February 5, 2024 11:25 AM, Achu Luma wrote:\n>In a following commit we are going to port code from \"t/helper/test-date.c\"\nand\n>\"t/t0006-date.sh\" to a new \"t/unit-tests/t-date.c\" file using the recently\nadded unit\n>test framework.\n>\n>We cannot fully port all the code from \"t/helper/test-date.c\" though, as\nthe test-\n>tool date helper is still used by a number of \"t/*.sh\" tests.\n>The TIME_IS_64BIT and TIME_T_IS_64BIT prereqs are especially used by\n\"t5000-\n>tar-tree.sh\", \"t5318-commit-graph.sh\" and\n\"t5328-commit-graph-64bit-time.sh\"\n>while checking those prereqs will be required in the new\n\"t/unit-tests/t-date.c\" file\n>too.\n>\n>To avoid duplicating in both \"t/helper/test-date.c\" and\n\"t/unit-tests/t-date.c\" the\n>small amount of code checking these prereqs, let's move it into inline\nfunctions in\n>\"date.h\".\n>\n>The names of these new inline functions contain \"TIME_IS_64BIT\" or\n>\"TIME_T_IS_64BIT\" as it will simplify the macros we will use when we will\nport code\n>to \"t/unit-tests/t-date.c\" in a following commit.\n>\n>Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n>Signed-off-by: Achu Luma <ach.lumap@gmail.com>\n>---\n> date.h               | 6 ++++++\n> t/helper/test-date.c | 4 ++--\n> 2 files changed, 8 insertions(+), 2 deletions(-)\n>\n>diff --git a/date.h b/date.h\n>index 6136212a19..fb70490a51 100644\n>--- a/date.h\n>+++ b/date.h\n>@@ -70,4 +70,10 @@ void datestamp(struct strbuf *out);  timestamp_t\n>approxidate_careful(const char *, int *);  int date_overflows(timestamp_t\ndate);\n>time_t tm_to_time_t(const struct tm *tm);\n>+static inline int check_prereq_TIME_IS_64BIT(void) {\n>+\treturn sizeof(timestamp_t) == 8;\n>+}\n>+static inline int check_prereq_TIME_T_IS_64BIT(void) {\n>+\treturn sizeof(time_t) == 8;\n>+}\n> #endif\n>diff --git a/t/helper/test-date.c b/t/helper/test-date.c index\n>0683d46574..be0b8679c3 100644\n>--- a/t/helper/test-date.c\n>+++ b/t/helper/test-date.c\n>@@ -126,9 +126,9 @@ int cmd__date(int argc UNUSED, const char **argv)\n> \telse if (!strcmp(*argv, \"getnanos\"))\n> \t\tgetnanos(argv+1);\n> \telse if (!strcmp(*argv, \"is64bit\"))\n>-\t\treturn sizeof(timestamp_t) == 8 ? 0 : 1;\n>+\t\treturn !check_prereq_TIME_IS_64BIT();\n> \telse if (!strcmp(*argv, \"time_t-is64bit\"))\n>-\t\treturn sizeof(time_t) == 8 ? 0 : 1;\n>+\t\treturn !check_prereq_TIME_T_IS_64BIT();\n> \telse\n> \t\tusage(usage_msg);\n> \treturn 0;\n>--\n>2.43.0.windows.1\n\nI would suggest that you also take into account whether time_t is signed or\nnot (more difficult perhaps). Some platforms use signed time_t to allow\nrepresentation of dates prior to 1970-01-01, while others make this signed.\nSome other platforms (S/390 for example) have retained time_t as 32-bits but\nhave a time64_t for 64 bits. It might be useful to account for this.\n--Randall\n\n"},{"id":"488053","messageId":"CAP8UFD2pJOU7sAWROENcK+8g5J0oZpXPeQ9UOh5CwS+CvVpx=Q@mail.gmail.com","threadId":"60849","inReplyTo":"020a01da5859$89e41210$9dac3630$@nexbridge.com","subject":"Re: [Outreachy][PATCH 1/2] date: refactor 64 bit prereq code into reusable functions","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-06T08:39:27Z","receivedAt":"2024-02-06T08:39:41Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Feb 5, 2024 at 6:34 PM <rsbecker@nexbridge.com> wrote:\n> On Monday, February 5, 2024 11:25 AM, Achu Luma wrote:\n\n> I would suggest that you also take into account whether time_t is signed or\n> not (more difficult perhaps). Some platforms use signed time_t to allow\n> representation of dates prior to 1970-01-01, while others make this signed.\n> Some other platforms (S/390 for example) have retained time_t as 32-bits but\n> have a time64_t for 64 bits. It might be useful to account for this.\n\nThe goal of this small series is just to port some existing tests to\nthe new unit test framework. I think it's a different topic to improve\nthe existing tests to take into account whether time_t is signed or\nnot. But thanks for the info.\n"},{"id":"491747","messageId":"tpaamfc3g5mrrbfufyvxi67ja2ko2hiihrptwxkbmdx4qpid3f@7aashrngiscn","threadId":"60849","inReplyTo":"20240205162506.1835-2-ach.lumap@gmail.com","subject":"Re: [Outreachy][PATCH 2/2] Port helper/test-date.c to unit-tests/t-date.c","fromName":"Ghanshyam Thakkar","fromEmail":"shyamthakkar001@gmail.com","sentAt":"2024-03-28T12:35:59Z","receivedAt":"2024-03-28T12:36:04Z","isPatch":true,"sender":{"key":"shyamthakkar001@gmail.com","avatar":"https://avatars.githubusercontent.com/u/72698233?v=4"},"body":"On 24/02/05 05:25PM, Achu Luma wrote:\n> In the recent codebase update (8bf6fbd (Merge branch\n> 'js/doc-unit-tests', 2023-12-09)), a new unit testing framework was\n> merged, providing a standardized approach for testing C code. Prior to\n> this update, some unit tests relied on the test helper mechanism,\n> lacking a dedicated unit testing framework. It's more natural to perform\n> these unit tests using the new unit test framework.\n> \n> This commit migrates the unit tests for C date functions\n> (show_date_relative(), show_date(), etc) from the legacy approach\n> using the test-tool command `test-tool date` in t/helper/test-date.c\n> to the new unit testing framework (t/unit-tests/test-lib.h).\n> \n> The migration involves refactoring the tests to utilize the testing\n> macros provided by the framework (TEST() and check_*()).\n> \n> Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Achu Luma <ach.lumap@gmail.com>\n> ---\n>  I am currently facing challenges in handling the time zone (TZ)\n>  variable, in t-date.c. The tests, which were initially passing\n>  in the t0006-date.sh  on Windows, fail after porting to the new\n>  framework. I have tried to set the timezone using the setenv,\n>  but unfortunately, the issue persists. I suspect that the problem\n>  might be related to how Windows C compilers process the setenv\n>  function compared to POSIX environments. Please If anyone has\n>  insights into managing environment variables in a cross-platform\n>  manner or has encountered similar issues, your help would be\n>  highly appreciated.\n\nI believe the issue might not be related to the setenv function, but rather\nwith tzset(). As you can see here[1], when we set TZ before we call the\nunit-testing binaries, the tests which were failing (EST5 ones that I\nseparated with t-datetest) pass on 'win test (0)', and the ones which\nwere passing (UTC ones, t-date) fail. (Although some tests on linux are also\nfailing, but that can be explained by the fact that t-date runs first\nand sets the TZ to UTC, afterwhich t-datetest runs and fails, although\nthis is not conclusive). Therefore, I am almost certain that the issue\nis with changing the timezone during runtime on windows and not with setting\nTZ variable with setenv(). CC'ing Johannes to see if he has any insights\non this.\n\nOther that that, there are many places in this patch where indentation\nis done with spaces instead of tabs, so it'd be good to change that.\n\nThanks.\n\n[1]:\nhttps://github.com/spectre10/git/actions/runs/8466649087/job/23196586161\n> \n>  t/helper/test-date.c  |  91 +---------------\n>  t/t0006-date.sh       | 169 ------------------------------\n>  t/unit-tests/t-date.c | 237 ++++++++++++++++++++++++++++++++++++++++++\n>  3 files changed, 238 insertions(+), 259 deletions(-)\n>  delete mode 100755 t/t0006-date.sh\n>  create mode 100644 t/unit-tests/t-date.c\n> \n> diff --git a/t/helper/test-date.c b/t/helper/test-date.c\n> index be0b8679c3..b9cb2c5455 100644\n> --- a/t/helper/test-date.c\n> +++ b/t/helper/test-date.c\n> @@ -3,89 +3,11 @@\n>  #include \"trace.h\"\n> \n>  static const char *usage_msg = \"\\n\"\n> -\"  test-tool date relative [time_t]...\\n\"\n> -\"  test-tool date human [time_t]...\\n\"\n> -\"  test-tool date show:<format> [time_t]...\\n\"\n> -\"  test-tool date parse [date]...\\n\"\n> -\"  test-tool date approxidate [date]...\\n\"\n>  \"  test-tool date timestamp [date]...\\n\"\n>  \"  test-tool date getnanos [start-nanos]\\n\"\n>  \"  test-tool date is64bit\\n\"\n>  \"  test-tool date time_t-is64bit\\n\";\n> \n> -static void show_relative_dates(const char **argv)\n> -{\n> -\tstruct strbuf buf = STRBUF_INIT;\n> -\n> -\tfor (; *argv; argv++) {\n> -\t\ttime_t t = atoi(*argv);\n> -\t\tshow_date_relative(t, &buf);\n> -\t\tprintf(\"%s -> %s\\n\", *argv, buf.buf);\n> -\t}\n> -\tstrbuf_release(&buf);\n> -}\n> -\n> -static void show_human_dates(const char **argv)\n> -{\n> -\tfor (; *argv; argv++) {\n> -\t\ttime_t t = atoi(*argv);\n> -\t\tprintf(\"%s -> %s\\n\", *argv, show_date(t, 0, DATE_MODE(HUMAN)));\n> -\t}\n> -}\n> -\n> -static void show_dates(const char **argv, const char *format)\n> -{\n> -\tstruct date_mode mode = DATE_MODE_INIT;\n> -\n> -\tparse_date_format(format, &mode);\n> -\tfor (; *argv; argv++) {\n> -\t\tchar *arg;\n> -\t\ttimestamp_t t;\n> -\t\tint tz;\n> -\n> -\t\t/*\n> -\t\t * Do not use our normal timestamp parsing here, as the point\n> -\t\t * is to test the formatting code in isolation.\n> -\t\t */\n> -\t\tt = parse_timestamp(*argv, &arg, 10);\n> -\t\twhile (*arg == ' ')\n> -\t\t\targ++;\n> -\t\ttz = atoi(arg);\n> -\n> -\t\tprintf(\"%s -> %s\\n\", *argv, show_date(t, tz, &mode));\n> -\t}\n> -\n> -\tdate_mode_release(&mode);\n> -}\n> -\n> -static void parse_dates(const char **argv)\n> -{\n> -\tstruct strbuf result = STRBUF_INIT;\n> -\n> -\tfor (; *argv; argv++) {\n> -\t\ttimestamp_t t;\n> -\t\tint tz;\n> -\n> -\t\tstrbuf_reset(&result);\n> -\t\tparse_date(*argv, &result);\n> -\t\tif (sscanf(result.buf, \"%\"PRItime\" %d\", &t, &tz) == 2)\n> -\t\t\tprintf(\"%s -> %s\\n\",\n> -\t\t\t       *argv, show_date(t, tz, DATE_MODE(ISO8601)));\n> -\t\telse\n> -\t\t\tprintf(\"%s -> bad\\n\", *argv);\n> -\t}\n> -\tstrbuf_release(&result);\n> -}\n> -\n> -static void parse_approxidate(const char **argv)\n> -{\n> -\tfor (; *argv; argv++) {\n> -\t\ttimestamp_t t;\n> -\t\tt = approxidate(*argv);\n> -\t\tprintf(\"%s -> %s\\n\", *argv, show_date(t, 0, DATE_MODE(ISO8601)));\n> -\t}\n> -}\n> -\n>  static void parse_approx_timestamp(const char **argv)\n>  {\n>  \tfor (; *argv; argv++) {\n> @@ -106,22 +28,11 @@ static void getnanos(const char **argv)\n> \n>  int cmd__date(int argc UNUSED, const char **argv)\n>  {\n> -\tconst char *x;\n> \n>  \targv++;\n>  \tif (!*argv)\n>  \t\tusage(usage_msg);\n> -\tif (!strcmp(*argv, \"relative\"))\n> -\t\tshow_relative_dates(argv+1);\n> -\telse if (!strcmp(*argv, \"human\"))\n> -\t\tshow_human_dates(argv+1);\n> -\telse if (skip_prefix(*argv, \"show:\", &x))\n> -\t\tshow_dates(argv+1, x);\n> -\telse if (!strcmp(*argv, \"parse\"))\n> -\t\tparse_dates(argv+1);\n> -\telse if (!strcmp(*argv, \"approxidate\"))\n> -\t\tparse_approxidate(argv+1);\n> -\telse if (!strcmp(*argv, \"timestamp\"))\n> +\tif (!strcmp(*argv, \"timestamp\"))\n>  \t\tparse_approx_timestamp(argv+1);\n>  \telse if (!strcmp(*argv, \"getnanos\"))\n>  \t\tgetnanos(argv+1);\n> diff --git a/t/t0006-date.sh b/t/t0006-date.sh\n> deleted file mode 100755\n> index e18b160286..0000000000\n> --- a/t/t0006-date.sh\n> +++ /dev/null\n> @@ -1,169 +0,0 @@\n> -#!/bin/sh\n> -\n> -test_description='test date parsing and printing'\n> -\n> -TEST_PASSES_SANITIZE_LEAK=true\n> -. ./test-lib.sh\n> -\n> -# arbitrary reference time: 2009-08-30 19:20:00\n> -GIT_TEST_DATE_NOW=1251660000; export GIT_TEST_DATE_NOW\n> -\n> -check_relative() {\n> -\tt=$(($GIT_TEST_DATE_NOW - $1))\n> -\techo \"$t -> $2\" >expect\n> -\ttest_expect_${3:-success} \"relative date ($2)\" \"\n> -\ttest-tool date relative $t >actual &&\n> -\ttest_cmp expect actual\n> -\t\"\n> -}\n> -\n> -check_relative 5 '5 seconds ago'\n> -check_relative 300 '5 minutes ago'\n> -check_relative 18000 '5 hours ago'\n> -check_relative 432000 '5 days ago'\n> -check_relative 1728000 '3 weeks ago'\n> -check_relative 13000000 '5 months ago'\n> -check_relative 37500000 '1 year, 2 months ago'\n> -check_relative 55188000 '1 year, 9 months ago'\n> -check_relative 630000000 '20 years ago'\n> -check_relative 31449600 '12 months ago'\n> -check_relative 62985600 '2 years ago'\n> -\n> -check_show () {\n> -\tformat=$1\n> -\ttime=$2\n> -\texpect=$3\n> -\tprereqs=$4\n> -\tzone=$5\n> -\ttest_expect_success $prereqs \"show date ($format:$time)\" '\n> -\t\techo \"$time -> $expect\" >expect &&\n> -\t\tTZ=${zone:-$TZ} test-tool date show:\"$format\" \"$time\" >actual &&\n> -\t\ttest_cmp expect actual\n> -\t'\n> -}\n> -\n> -# arbitrary but sensible time for examples\n> -TIME='1466000000 +0200'\n> -check_show iso8601 \"$TIME\" '2016-06-15 16:13:20 +0200'\n> -check_show iso8601-strict \"$TIME\" '2016-06-15T16:13:20+02:00'\n> -check_show rfc2822 \"$TIME\" 'Wed, 15 Jun 2016 16:13:20 +0200'\n> -check_show short \"$TIME\" '2016-06-15'\n> -check_show default \"$TIME\" 'Wed Jun 15 16:13:20 2016 +0200'\n> -check_show raw \"$TIME\" '1466000000 +0200'\n> -check_show unix \"$TIME\" '1466000000'\n> -check_show iso-local \"$TIME\" '2016-06-15 14:13:20 +0000'\n> -check_show raw-local \"$TIME\" '1466000000 +0000'\n> -check_show unix-local \"$TIME\" '1466000000'\n> -\n> -check_show 'format:%z' \"$TIME\" '+0200'\n> -check_show 'format-local:%z' \"$TIME\" '+0000'\n> -check_show 'format:%Z' \"$TIME\" ''\n> -check_show 'format-local:%Z' \"$TIME\" 'UTC'\n> -check_show 'format:%%z' \"$TIME\" '%z'\n> -check_show 'format-local:%%z' \"$TIME\" '%z'\n> -\n> -check_show 'format:%Y-%m-%d %H:%M:%S' \"$TIME\" '2016-06-15 16:13:20'\n> -check_show 'format-local:%Y-%m-%d %H:%M:%S' \"$TIME\" '2016-06-15 09:13:20' '' EST5\n> -\n> -check_show 'format:%s' '123456789 +1234' 123456789\n> -check_show 'format:%s' '123456789 -1234' 123456789\n> -check_show 'format-local:%s' '123456789 -1234' 123456789\n> -\n> -# arbitrary time absurdly far in the future\n> -FUTURE=\"5758122296 -0400\"\n> -check_show iso       \"$FUTURE\" \"2152-06-19 18:24:56 -0400\" TIME_IS_64BIT,TIME_T_IS_64BIT\n> -check_show iso-local \"$FUTURE\" \"2152-06-19 22:24:56 +0000\" TIME_IS_64BIT,TIME_T_IS_64BIT\n> -\n> -check_parse() {\n> -\techo \"$1 -> $2\" >expect\n> -\ttest_expect_${4:-success} \"parse date ($1${3:+ TZ=$3})\" \"\n> -\tTZ=${3:-$TZ} test-tool date parse '$1' >actual &&\n> -\ttest_cmp expect actual\n> -\t\"\n> -}\n> -\n> -check_parse 2008 bad\n> -check_parse 2008-02 bad\n> -check_parse 2008-02-14 bad\n> -check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 +0000'\n> -check_parse '2008-02-14 20:30:45 -0500' '2008-02-14 20:30:45 -0500'\n> -check_parse '2008.02.14 20:30:45 -0500' '2008-02-14 20:30:45 -0500'\n> -check_parse '20080214T20:30:45' '2008-02-14 20:30:45 +0000'\n> -check_parse '20080214T20:30' '2008-02-14 20:30:00 +0000'\n> -check_parse '20080214T20' '2008-02-14 20:00:00 +0000'\n> -check_parse '20080214T203045' '2008-02-14 20:30:45 +0000'\n> -check_parse '20080214T2030' '2008-02-14 20:30:00 +0000'\n> -check_parse '20080214T000000.20' '2008-02-14 00:00:00 +0000'\n> -check_parse '20080214T00:00:00.20' '2008-02-14 00:00:00 +0000'\n> -check_parse '20080214T203045-04:00' '2008-02-14 20:30:45 -0400'\n> -check_parse '20080214T203045 -04:00' '2008-02-14 20:30:45 -0400'\n> -check_parse '20080214T203045.019-04:00' '2008-02-14 20:30:45 -0400'\n> -check_parse '2008-02-14 20:30:45.019-04:00' '2008-02-14 20:30:45 -0400'\n> -check_parse '2008-02-14 20:30:45 -0015' '2008-02-14 20:30:45 -0015'\n> -check_parse '2008-02-14 20:30:45 -5' '2008-02-14 20:30:45 +0000'\n> -check_parse '2008-02-14 20:30:45 -5:' '2008-02-14 20:30:45 +0000'\n> -check_parse '2008-02-14 20:30:45 -05' '2008-02-14 20:30:45 -0500'\n> -check_parse '2008-02-14 20:30:45 -:30' '2008-02-14 20:30:45 +0000'\n> -check_parse '2008-02-14 20:30:45 -05:00' '2008-02-14 20:30:45 -0500'\n> -check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 -0500' EST5\n> -check_parse 'Thu, 7 Apr 2005 15:14:13 -0700' '2005-04-07 15:14:13 -0700'\n> -\n> -check_approxidate() {\n> -\techo \"$1 -> $2 +0000\" >expect\n> -\ttest_expect_${3:-success} \"parse approxidate ($1)\" \"\n> -\ttest-tool date approxidate '$1' >actual &&\n> -\ttest_cmp expect actual\n> -\t\"\n> -}\n> -\n> -check_approxidate now '2009-08-30 19:20:00'\n> -check_approxidate '5 seconds ago' '2009-08-30 19:19:55'\n> -check_approxidate 5.seconds.ago '2009-08-30 19:19:55'\n> -check_approxidate 10.minutes.ago '2009-08-30 19:10:00'\n> -check_approxidate yesterday '2009-08-29 19:20:00'\n> -check_approxidate 3.days.ago '2009-08-27 19:20:00'\n> -check_approxidate '12:34:56.3.days.ago' '2009-08-27 12:34:56'\n> -check_approxidate 3.weeks.ago '2009-08-09 19:20:00'\n> -check_approxidate 3.months.ago '2009-05-30 19:20:00'\n> -check_approxidate 2.years.3.months.ago '2007-05-30 19:20:00'\n> -\n> -check_approxidate '6am yesterday' '2009-08-29 06:00:00'\n> -check_approxidate '6pm yesterday' '2009-08-29 18:00:00'\n> -check_approxidate '3:00' '2009-08-30 03:00:00'\n> -check_approxidate '15:00' '2009-08-30 15:00:00'\n> -check_approxidate 'noon today' '2009-08-30 12:00:00'\n> -check_approxidate 'noon yesterday' '2009-08-29 12:00:00'\n> -check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00'\n> -check_approxidate '10am noon' '2009-08-29 12:00:00'\n> -\n> -check_approxidate 'last tuesday' '2009-08-25 19:20:00'\n> -check_approxidate 'July 5th' '2009-07-05 19:20:00'\n> -check_approxidate '06/05/2009' '2009-06-05 19:20:00'\n> -check_approxidate '06.05.2009' '2009-05-06 19:20:00'\n> -\n> -check_approxidate 'Jun 6, 5AM' '2009-06-06 05:00:00'\n> -check_approxidate '5AM Jun 6' '2009-06-06 05:00:00'\n> -check_approxidate '6AM, June 7, 2009' '2009-06-07 06:00:00'\n> -\n> -check_approxidate '2008-12-01' '2008-12-01 19:20:00'\n> -check_approxidate '2009-12-01' '2009-12-01 19:20:00'\n> -\n> -check_date_format_human() {\n> -\tt=$(($GIT_TEST_DATE_NOW - $1))\n> -\techo \"$t -> $2\" >expect\n> -\ttest_expect_success \"human date $t\" '\n> -\t\ttest-tool date human $t >actual &&\n> -\t\ttest_cmp expect actual\n> -'\n> -}\n> -\n> -check_date_format_human 18000 \"5 hours ago\" # 5 hours ago\n> -check_date_format_human 432000 \"Tue Aug 25 19:20\" # 5 days ago\n> -check_date_format_human 1728000 \"Mon Aug 10 19:20\" # 3 weeks ago\n> -check_date_format_human 13000000 \"Thu Apr 2 08:13\" # 5 months ago\n> -check_date_format_human 31449600 \"Aug 31 2008\" # 12 months ago\n> -check_date_format_human 37500000 \"Jun 22 2008\" # 1 year, 2 months ago\n> -check_date_format_human 55188000 \"Dec 1 2007\" # 1 year, 9 months ago\n> -check_date_format_human 630000000 \"Sep 13 1989\" # 20 years ago\n> -\n> -test_done\n> diff --git a/t/unit-tests/t-date.c b/t/unit-tests/t-date.c\n> new file mode 100644\n> index 0000000000..dd5dbbb2e0\n> --- /dev/null\n> +++ b/t/unit-tests/t-date.c\n> @@ -0,0 +1,237 @@\n> +#include \"test-lib.h\"\n> +#include \"date.h\"\n> +#include \"strbuf.h\"\n> +\n> +/* Reference time: 2009-08-30 19:20:00. */\n> +#define GIT_TEST_DATE_NOW 1251660000\n> +\n> +/* The time corresponds to Wed, 15 Jun 2016 16:13:20 +0200. */\n> +static const char test_time[] = \"1466000000 +0200\";\n> +\n> +enum prerequisites {\n> +    \tTIME_IS_64BIT = 1 << 0,\n> +    \tTIME_T_IS_64BIT = 1 << 1,\n> +};\n> +\n> +/* Macro to check prerequisites */\n> +#define CHECK_PREREQ(var, prereq) \\\n> +    \tdo { \\\n> +\t\tif ((var) & prereq && !check_prereq_##prereq()) { \\\n> +\t\t\ttest_skip(\"missing prerequisite \" #prereq); \\\n> +\t\t\treturn 0; \\\n> +\t\t} \\\n> +\t} while (0)\n> +\n> +/* Return 1 if all prereqs are satisfied, 0 otherwise */\n> +static int check_prereqs(unsigned int prereqs) {\n> +    \tCHECK_PREREQ(prereqs, TIME_IS_64BIT);\n> +    \tCHECK_PREREQ(prereqs, TIME_T_IS_64BIT);\n> +\n> +    \treturn 1;\n> +}\n> +\n> +static void set_TZ_env(const char *zone) {\n> +\tsetenv(\"TZ\", zone, 1);\n> +\ttzset();\n> +}\n> +\n> +static void check_relative_dates(int time_val, const char *expected_date) {\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\ttimestamp_t diff = GIT_TEST_DATE_NOW - time_val;\n> +\n> +\tshow_date_relative(diff, &buf);\n> +\tcheck_str(buf.buf, expected_date);\n> +\tstrbuf_release(&buf);\n> +}\n> +\n> +#define TEST_RELATIVE_DATE(value, expected_output) \\\n> +    \tTEST(check_relative_dates(value, expected_output), \\\n> +        \t\"relative date (%s) works\", #expected_output )\n> +\n> +static void check_show_date(const char *format, const char *TIME, const char *expected, unsigned int prereqs, const char *zone) {\n> +\tstruct date_mode mode = DATE_MODE_INIT;\n> +\tchar *arg;\n> +\ttimestamp_t t;\n> +\tint tz;\n> +\n> +\tif (!check_prereqs(prereqs))\n> +\t\treturn;\n> +\tif (strcmp(zone, \"\"))\n> +\t\tset_TZ_env(zone);\n> +\n> +\tparse_date_format(format, &mode);\n> +\tt = parse_timestamp(TIME, &arg, 10);\n> +\ttz = atoi(arg);\n> +\n> +\tcheck_str(show_date(t, tz, &mode), expected);\n> +\n> +\tif (strcmp(zone, \"\"))\n> +\t\tset_TZ_env(\"UTC\");\n> +\tdate_mode_release(&mode);\n> +}\n> +\n> +#define TEST_SHOW_DATE(format, time, expected, prereqs, zone) \\\n> +\tTEST(check_show_date(format, time, expected, prereqs, zone), \\\n> +\t     \"show date (%s) works\", #format)\n> +\n> +static void check_parse_date(const char *given, const char *expected, const char *zone) {\n> +\tstruct strbuf result = STRBUF_INIT;\n> +\ttimestamp_t t;\n> +\tint tz;\n> +\n> +\tif (strcmp(zone, \"\"))\n> +\t\tset_TZ_env(zone);\n> +\n> +\tparse_date(given, &result);\n> +\tif (sscanf(result.buf, \"%\"PRItime\" %d\", &t, &tz) == 2)\n> +\t\tcheck_str(show_date(t, tz, DATE_MODE(ISO8601)), expected);\n> +\telse\n> +\t\tcheck_str(\"bad\", expected);\n> +\n> +\tif (strcmp(zone, \"\"))\n> +\t\tset_TZ_env(\"UTC\");\n> +\tstrbuf_release(&result);\n> +}\n> +\n> +#define TEST_PARSE_DATE(given, expected_output, zone) \\\n> +    \tTEST(check_parse_date(given, expected_output, zone), \\\n> +        \t\"parse date (%s) works\", #expected_output)\n> +\n> +static void check_approxidate(const char *given, const char *expected) {\n> +\ttimestamp_t t = approxidate(given);\n> +\tchar *expected_with_offset = xstrfmt(\"%s +0000\", expected);\n> +\n> +\tcheck_str(show_date(t, 0, DATE_MODE(ISO8601)), expected_with_offset);\n> +\tfree(expected_with_offset);\n> +}\n> +\n> +#define TEST_APPROXIDATE(given, expected_output) \\\n> +    \tTEST(check_approxidate(given, expected_output), \\\n> +        \t\"parse approxidate (%s) works\", #given)\n> +\n> +static void check_date_format_human(int given, const char *expected) {\n> +\ttimestamp_t diff = GIT_TEST_DATE_NOW - given;\n> +\tcheck_str(show_date(diff, 0, DATE_MODE(HUMAN)), expected);\n> +}\n> +\n> +#define TEST_DATE_FORMAT_HUMAN(given, expected_output) \\\n> +    \tTEST(check_date_format_human(given, expected_output), \\\n> +        \t\"human date (%s) works\", #given)\n> +\n> +int cmd_main(int argc, const char **argv) {\n> +\tset_TZ_env(\"UTC\");\n> +\tsetenv(\"GIT_TEST_DATE_NOW\", \"1251660000\", 1);\n> +\tsetenv(\"LANG\", \"C\", 1);\n> +\n> +\tTEST_RELATIVE_DATE(5, \"5 seconds ago\");\n> +\tTEST_RELATIVE_DATE(300, \"5 minutes ago\");\n> +\tTEST_RELATIVE_DATE(18000, \"5 hours ago\");\n> +\tTEST_RELATIVE_DATE(432000, \"5 days ago\");\n> +\tTEST_RELATIVE_DATE(1728000, \"3 weeks ago\");\n> +\tTEST_RELATIVE_DATE(13000000, \"5 months ago\");\n> +\tTEST_RELATIVE_DATE(37500000, \"1 year, 2 months ago\");\n> +\tTEST_RELATIVE_DATE(55188000, \"1 year, 9 months ago\");\n> +\tTEST_RELATIVE_DATE(630000000, \"20 years ago\");\n> +\tTEST_RELATIVE_DATE(31449600, \"12 months ago\");\n> +\tTEST_RELATIVE_DATE(62985600, \"2 years ago\");\n> +\n> +\tTEST_SHOW_DATE(\"iso8601\", test_time, \"2016-06-15 16:13:20 +0200\", 0, \"\");\n> +\tTEST_SHOW_DATE(\"iso8601-strict\", test_time, \"2016-06-15T16:13:20+02:00\", 0, \"\");\n> +\tTEST_SHOW_DATE(\"rfc2822\", test_time, \"Wed, 15 Jun 2016 16:13:20 +0200\", 0, \"\");\n> +\tTEST_SHOW_DATE(\"short\", test_time, \"2016-06-15\", 0, \"\");\n> +\tTEST_SHOW_DATE(\"default\", test_time, \"Wed Jun 15 16:13:20 2016 +0200\", 0, \"\");\n> +\tTEST_SHOW_DATE(\"raw\", test_time, test_time, 0, \"\");\n> +\tTEST_SHOW_DATE(\"unix\", test_time, \"1466000000\", 0, \"\");\n> +\tTEST_SHOW_DATE(\"iso-local\", test_time, \"2016-06-15 14:13:20 +0000\", 0, \"\");\n> +\tTEST_SHOW_DATE(\"raw-local\", test_time, \"1466000000 +0000\", 0, \"\");\n> +\tTEST_SHOW_DATE(\"unix-local\", test_time, \"1466000000\", 0, \"\");\n> +\n> +\tTEST_SHOW_DATE(\"format:%z\", test_time, \"+0200\", 0, \"\");\n> +\tTEST_SHOW_DATE(\"format-local:%z\", test_time, \"+0000\", 0, \"\");\n> +\tTEST_SHOW_DATE(\"format:%Z\", test_time, \"\", 0, \"\");\n> +\tTEST_SHOW_DATE(\"format-local:%Z\", test_time, \"UTC\", 0, \"\");\n> +\tTEST_SHOW_DATE(\"format:%%z\", test_time, \"%z\", 0, \"\");\n> +\tTEST_SHOW_DATE(\"format-local:%%z\", test_time, \"%z\", 0, \"\");\n> +\n> +\tTEST_SHOW_DATE(\"format:%Y-%m-%d %H:%M:%S\", test_time, \"2016-06-15 16:13:20\", 0, \"\");\n> +\n> +\tTEST_SHOW_DATE(\"format-local:%Y-%m-%d %H:%M:%S\", test_time, \"2016-06-15 09:13:20\", 0, \"EST5\");\n> +\n> +\tTEST_SHOW_DATE(\"format:%s\", \"123456789 +1234\", \"123456789\", 0, \"\");\n> +\tTEST_SHOW_DATE(\"format:%s\", \"123456789 -1234\", \"123456789\", 0, \"\");\n> +\tTEST_SHOW_DATE(\"format-local:%s\", \"123456789 -1234\", \"123456789\", 0, \"\");\n> +\n> +\t/* Arbitrary time absurdly far in the future */\n> +\tTEST_SHOW_DATE(\"iso\", \"5758122296 -0400\", \"2152-06-19 18:24:56 -0400\", TIME_IS_64BIT | TIME_T_IS_64BIT, \"\");\n> +\tTEST_SHOW_DATE(\"iso-local\", \"5758122296 -0400\", \"2152-06-19 22:24:56 +0000\", TIME_IS_64BIT | TIME_T_IS_64BIT, \"\");\n> +\n> +\tTEST_PARSE_DATE(\"2000\", \"bad\", \"\");\n> +\tTEST_PARSE_DATE(\"2008-02\", \"bad\", \"\");\n> +\tTEST_PARSE_DATE(\"2008-02-14\", \"bad\", \"\");\n> +\tTEST_PARSE_DATE(\"2008-02-14 20:30:45\", \"2008-02-14 20:30:45 +0000\", \"\");\n> +\tTEST_PARSE_DATE(\"2008-02-14 20:30:45 -0500\", \"2008-02-14 20:30:45 -0500\", \"\");\n> +\tTEST_PARSE_DATE(\"2008.02.14 20:30:45 -0500\", \"2008-02-14 20:30:45 -0500\", \"\");\n> +\tTEST_PARSE_DATE(\"20080214T20:30:45\", \"2008-02-14 20:30:45 +0000\", \"\");\n> +\tTEST_PARSE_DATE(\"20080214T20:30\", \"2008-02-14 20:30:00 +0000\", \"\");\n> +\tTEST_PARSE_DATE(\"20080214T20\", \"2008-02-14 20:00:00 +0000\", \"\");\n> +\tTEST_PARSE_DATE(\"20080214T203045\", \"2008-02-14 20:30:45 +0000\", \"\");\n> +\tTEST_PARSE_DATE(\"20080214T2030\", \"2008-02-14 20:30:00 +0000\", \"\");\n> +\tTEST_PARSE_DATE(\"20080214T000000.20\", \"2008-02-14 00:00:00 +0000\", \"\");\n> +\tTEST_PARSE_DATE(\"20080214T00:00:00.20\", \"2008-02-14 00:00:00 +0000\", \"\");\n> +\tTEST_PARSE_DATE(\"20080214T203045-04:00\", \"2008-02-14 20:30:45 -0400\", \"\");\n> +\n> +\tTEST_PARSE_DATE(\"20080214T203045 -04:00\", \"2008-02-14 20:30:45 -0400\", \"\");\n> +\tTEST_PARSE_DATE(\"20080214T203045.019-04:00\", \"2008-02-14 20:30:45 -0400\", \"\");\n> +\tTEST_PARSE_DATE(\"2008-02-14 20:30:45.019-04:00\", \"2008-02-14 20:30:45 -0400\", \"\");\n> +\tTEST_PARSE_DATE(\"2008-02-14 20:30:45 -0015\", \"2008-02-14 20:30:45 -0015\", \"\");\n> +\tTEST_PARSE_DATE(\"2008-02-14 20:30:45 -5\", \"2008-02-14 20:30:45 +0000\", \"\");\n> +\tTEST_PARSE_DATE(\"2008-02-14 20:30:45 -5:\", \"2008-02-14 20:30:45 +0000\", \"\");\n> +\tTEST_PARSE_DATE(\"2008-02-14 20:30:45 -05\", \"2008-02-14 20:30:45 -0500\", \"\");\n> +\tTEST_PARSE_DATE(\"2008-02-14 20:30:45 -:30\", \"2008-02-14 20:30:45 +0000\", \"\");\n> +\tTEST_PARSE_DATE(\"2008-02-14 20:30:45 -05:00\", \"2008-02-14 20:30:45 -0500\", \"\");\n> +\tTEST_PARSE_DATE(\"2008-02-14 20:30:45\", \"2008-02-14 20:30:45 -0500\", \"EST5\");\n> +\n> +\tTEST_PARSE_DATE(\"Thu, 7 Apr 2005 15:14:13 -0700\", \"2005-04-07 15:14:13 -0700\", \"\");\n> +\n> +\tTEST_APPROXIDATE(\"now\", \"2009-08-30 19:20:00\");\n> +\tTEST_APPROXIDATE(\"5 seconds ago\", \"2009-08-30 19:19:55\");\n> +\tTEST_APPROXIDATE(\"10 minutes ago\", \"2009-08-30 19:10:00\");\n> +\tTEST_APPROXIDATE(\"yesterday\", \"2009-08-29 19:20:00\");\n> +\tTEST_APPROXIDATE(\"3 days ago\", \"2009-08-27 19:20:00\");\n> +\tTEST_APPROXIDATE(\"12:34:56.3 days ago\", \"2009-08-27 12:34:56\");\n> +\tTEST_APPROXIDATE(\"3 weeks ago\", \"2009-08-09 19:20:00\");\n> +\tTEST_APPROXIDATE(\"3 months ago\", \"2009-05-30 19:20:00\");\n> +\tTEST_APPROXIDATE(\"2 years 3 months ago\", \"2007-05-30 19:20:00\");\n> +\n> +\tTEST_APPROXIDATE(\"6am yesterday\", \"2009-08-29 06:00:00\");\n> +\tTEST_APPROXIDATE(\"6pm yesterday\", \"2009-08-29 18:00:00\");\n> +\tTEST_APPROXIDATE(\"3:00\", \"2009-08-30 03:00:00\");\n> +\tTEST_APPROXIDATE(\"15:00\", \"2009-08-30 15:00:00\");\n> +\tTEST_APPROXIDATE(\"noon today\", \"2009-08-30 12:00:00\");\n> +\tTEST_APPROXIDATE(\"noon yesterday\", \"2009-08-29 12:00:00\");\n> +\tTEST_APPROXIDATE(\"January 5th noon pm\", \"2009-01-05 12:00:00\");\n> +\tTEST_APPROXIDATE(\"10am noon\", \"2009-08-29 12:00:00\");\n> +\n> +\tTEST_APPROXIDATE(\"last tuesday\", \"2009-08-25 19:20:00\");\n> +\tTEST_APPROXIDATE(\"July 5th\", \"2009-07-05 19:20:00\");\n> +\tTEST_APPROXIDATE(\"06/05/2009\", \"2009-06-05 19:20:00\");\n> +\tTEST_APPROXIDATE(\"06.05.2009\", \"2009-05-06 19:20:00\");\n> +\n> +\tTEST_APPROXIDATE(\"Jun 6, 5AM\", \"2009-06-06 05:00:00\");\n> +\tTEST_APPROXIDATE(\"5AM Jun 6\", \"2009-06-06 05:00:00\");\n> +\tTEST_APPROXIDATE(\"6AM, June 7, 2009\", \"2009-06-07 06:00:00\");\n> +\n> +\tTEST_APPROXIDATE(\"2008-12-01\", \"2008-12-01 19:20:00\");\n> +\tTEST_APPROXIDATE(\"2009-12-01\", \"2009-12-01 19:20:00\");\n> +\n> +\tTEST_DATE_FORMAT_HUMAN(18000, \"5 hours ago\");\n> +\tTEST_DATE_FORMAT_HUMAN(432000, \"Tue Aug 25 19:20\");\n> +\tTEST_DATE_FORMAT_HUMAN(1728000, \"Mon Aug 10 19:20\");\n> +\tTEST_DATE_FORMAT_HUMAN(13000000, \"Thu Apr 2 08:13\");\n> +\tTEST_DATE_FORMAT_HUMAN(31449600, \"Aug 31 2008\");\n> +\tTEST_DATE_FORMAT_HUMAN(37500000, \"Jun 22 2008\");\n> +\tTEST_DATE_FORMAT_HUMAN(55188000, \"Dec 1 2007\");\n> +\tTEST_DATE_FORMAT_HUMAN(630000000, \"Sep 13 1989\");\n> +\n> +\treturn test_done();\n> +}\n> --\n> 2.43.0.windows.1\n> \n> \n"},{"id":"491759","messageId":"xmqqttkquxes.fsf@gitster.g","threadId":"60849","inReplyTo":"tpaamfc3g5mrrbfufyvxi67ja2ko2hiihrptwxkbmdx4qpid3f@7aashrngiscn","subject":"Re: [Outreachy][PATCH 2/2] Port helper/test-date.c to unit-tests/t-date.c","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-03-28T16:35:39Z","receivedAt":"2024-03-28T16:35:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ghanshyam Thakkar <shyamthakkar001@gmail.com> writes:\n\n> I believe the issue might not be related to the setenv function, but rather\n> with tzset(). As you can see here[1], when we set TZ before we call the\n> unit-testing binaries, the tests which were failing (EST5 ones that I\n> separated with t-datetest) pass on 'win test (0)', and the ones which\n> were passing (UTC ones, t-date) fail. (Although some tests on linux are also\n> failing, but that can be explained by the fact that t-date runs first\n> and sets the TZ to UTC, afterwhich t-datetest runs and fails, although\n> this is not conclusive). Therefore, I am almost certain that the issue\n> is with changing the timezone during runtime on windows and not with setting\n> TZ variable with setenv(). CC'ing Johannes to see if he has any insights\n> on this.\n\nInteresting.  Sometime before I started working on Git, I learned\nthat no program did tzset() after it started running to switch\nmultiple timezones and worked correctly on many different variants\nof UNIXes (there were many of them back then), and because I never\ngot interested in writing a world-clock program, I didn't know, and\nkind of surprised to learn that it works on some platforms (like\nLinux and macOS) to switch zones with tzset() these days ;-).\n\nSo, if Windows runtime is unhappy with the program calling tzset()\nmore than once, I wouldn't be too surprised.\n\nThanks.\n"},{"id":"495750","messageId":"ZlXaDWy0lQA1FM7d@tanuki","threadId":"60849","inReplyTo":"xmqqttkquxes.fsf@gitster.g","subject":"Re: [Outreachy][PATCH 2/2] Port helper/test-date.c to unit-tests/t-date.c","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-28T13:20:13Z","receivedAt":"2024-05-28T13:20:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Mar 28, 2024 at 09:35:39AM -0700, Junio C Hamano wrote:\n> Ghanshyam Thakkar <shyamthakkar001@gmail.com> writes:\n> \n> > I believe the issue might not be related to the setenv function, but rather\n> > with tzset(). As you can see here[1], when we set TZ before we call the\n> > unit-testing binaries, the tests which were failing (EST5 ones that I\n> > separated with t-datetest) pass on 'win test (0)', and the ones which\n> > were passing (UTC ones, t-date) fail. (Although some tests on linux are also\n> > failing, but that can be explained by the fact that t-date runs first\n> > and sets the TZ to UTC, afterwhich t-datetest runs and fails, although\n> > this is not conclusive). Therefore, I am almost certain that the issue\n> > is with changing the timezone during runtime on windows and not with setting\n> > TZ variable with setenv(). CC'ing Johannes to see if he has any insights\n> > on this.\n> \n> Interesting.  Sometime before I started working on Git, I learned\n> that no program did tzset() after it started running to switch\n> multiple timezones and worked correctly on many different variants\n> of UNIXes (there were many of them back then), and because I never\n> got interested in writing a world-clock program, I didn't know, and\n> kind of surprised to learn that it works on some platforms (like\n> Linux and macOS) to switch zones with tzset() these days ;-).\n> \n> So, if Windows runtime is unhappy with the program calling tzset()\n> more than once, I wouldn't be too surprised.\n> \n> Thanks.\n\nAs I was debugging other Windows-specific issues in a VM already, Chris\nasked me to also have a look at this issue. And indeed, most of the\ntests fail deterministically. I also found a fix:\n\n    diff --git a/t/unit-tests/t-date.c b/t/unit-tests/t-date.c\n    index dd5dbbb2e0..2d7b1f085a 100644\n    --- a/t/unit-tests/t-date.c\n    +++ b/t/unit-tests/t-date.c\n    @@ -31,7 +31,7 @@ static int check_prereqs(unsigned int prereqs) {\n     }\n     \n     static void set_TZ_env(const char *zone) {\n    -\tsetenv(\"TZ\", zone, 1);\n    +\t_putenv_s(\"TZ\", zone);\n        tzset();\n     }\n\nI have no idea why that works though, and the fix is of course not\nportable. But with this change, the timezones do get picked up by\n`tzset()` and related date functions as expected.\n\nI'm quite dumb when it comes to the Windows API, so I don't have much of\na clue why this works. The documentation also didn't point out anything\nobvious. Dscho, do you happen to have an explanation for this?\n\nPatrick\n"},{"id":"495760","messageId":"xmqq7cfd7ut0.fsf@gitster.g","threadId":"60849","inReplyTo":"ZlXaDWy0lQA1FM7d@tanuki","subject":"Re: [Outreachy][PATCH 2/2] Port helper/test-date.c to unit-tests/t-date.c","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-28T16:41:47Z","receivedAt":"2024-05-28T16:41:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> As I was debugging other Windows-specific issues in a VM already, Chris\n> asked me to also have a look at this issue. And indeed, most of the\n> tests fail deterministically. I also found a fix:\n> ...\n>     -\tsetenv(\"TZ\", zone, 1);\n>     +\t_putenv_s(\"TZ\", zone);\n>         tzset();\n>      }\n>\n> I have no idea why that works though, and the fix is of course not\n> portable. But with this change, the timezones do get picked up by\n> `tzset()` and related date functions as expected.\n\nThe header compat/mingw.h already talks about implementing its own\nreplacement by making gitsetenv() call mingw_putenv().\n\ngitsetenv() emulates setenv() in terms of putenv(), and on Windows\nmingw_putenv() is what implements putenv(), so the difference you\nare observing is coming from the difference between mingw_putenv()\nand _putenv_s(), I would guess.  As the former is isolated within\ncompat/mingw.c, it would not involve any additional portability\nissues to redo the former in terms of the latter, I would imagine.\n\n> I'm quite dumb when it comes to the Windows API, so I don't have much of\n> a clue why this works. The documentation also didn't point out anything\n> obvious. Dscho, do you happen to have an explanation for this?\n\nThanks.\n"},{"id":"495775","messageId":"ZlbB_T8DkgmPeWQp@tanuki","threadId":"60849","inReplyTo":"xmqq7cfd7ut0.fsf@gitster.g","subject":"Re: [Outreachy][PATCH 2/2] Port helper/test-date.c to unit-tests/t-date.c","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T05:49:49Z","receivedAt":"2024-05-29T05:49:55Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, May 28, 2024 at 09:41:47AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > As I was debugging other Windows-specific issues in a VM already, Chris\n> > asked me to also have a look at this issue. And indeed, most of the\n> > tests fail deterministically. I also found a fix:\n> > ...\n> >     -\tsetenv(\"TZ\", zone, 1);\n> >     +\t_putenv_s(\"TZ\", zone);\n> >         tzset();\n> >      }\n> >\n> > I have no idea why that works though, and the fix is of course not\n> > portable. But with this change, the timezones do get picked up by\n> > `tzset()` and related date functions as expected.\n> \n> The header compat/mingw.h already talks about implementing its own\n> replacement by making gitsetenv() call mingw_putenv().\n> \n> gitsetenv() emulates setenv() in terms of putenv(), and on Windows\n> mingw_putenv() is what implements putenv(), so the difference you\n> are observing is coming from the difference between mingw_putenv()\n> and _putenv_s(), I would guess.  As the former is isolated within\n> compat/mingw.c, it would not involve any additional portability\n> issues to redo the former in terms of the latter, I would imagine.\n\nAh, thanks for the pointer. And indeed, `mingw_putenv()` uses\n`SetEnvironmentVariableW()` to provide the functionality. This is what\nMSDN has to say [1]:\n\n    getenv and _putenv use the copy of the environment pointed to by the\n    global variable _environ to access the environment. getenv operates\n    only on the data structures accessible to the run-time library and\n    not on the environment \"segment\" created for the process by the\n    operating system. Therefore, programs that use the envp argument to\n    main or wmain may retrieve invalid information.\n\nSo calling `SetEnvironmentVariableW()` will not affect calls to\n`getenv()`. See also issues like for example [2].\n\nThis works just fine for us because we also stub out `getenv()` to use\n`GetEnvironmentVariableW()`, which is its counterpart. But everything\nelse in the C runtime that uses `getenv()` will not see the new values,\nincluding our date-related functions. We don't ever set any of those\nenvironment variables though, except for now in this new unit test.\n\nNow the question is why we use `SetEnvironmentVariableW()` over\n`_putenv_s`, and whether changing it would be safe. If the answer is a\nstrict \"yes\" then we could do that, but if it's a \"maybe\" then I'd\nrather not want to change it just to make these unit tests work. In that\ncase, we might aim for a localized fix like the one I have posted.\n\nPatrick\n\n[1]: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getenv-wgetenv?view=msvc-170\n[2]: https://github.com/curl/curl/issues/4774\n"},{"id":"497741","messageId":"28c49738-002e-6a67-2000-cdcf1c892ab1@gmx.de","threadId":"60849","inReplyTo":"ZlbB_T8DkgmPeWQp@tanuki","subject":"Re: [Outreachy][PATCH 2/2] Port helper/test-date.c to unit-tests/t-date.c","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2024-06-27T06:49:27Z","receivedAt":"2024-06-27T06:49:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Patrick,\n\nOn Wed, 29 May 2024, Patrick Steinhardt wrote:\n\n> Now the question is why we use `SetEnvironmentVariableW()` over\n> `_putenv_s`, and whether changing it would be safe.\n\nThe reason is that Git for Windows internally uses UTF-8 _always_. But\n`_putenv_s()` knows nothing of that choice, it uses the \"active code\npage\", which -- you guessed it -- is not at all controlled by `LC_CTYPE`\nbut requires its own call to a Win32 Console API function.\n\nNow, there is theoretically that thing that you _could_ switch the active\nWin32 Console to CP_UTF8, i.e. the code page that corresponds to UTF-8.\nHowever, for that to work as well as Git for Windows' users deserve it, it\nwould require a recent Windows 10 version, and Git for Windows still tries\nto support Windows 7 and Windows 8 [*1*].\n\nFor that reason, Git for Windows performs the conversion from UTF-8 to\nUTF-16 and then uses the `*W()` Win32 API function that accepts Unicode\n(no matter what the current code page is).\n\nWith that in mind, I would love to find a solution that still uses that\n`*W()` Win32 API function.\n\nCiao,\nJohannes\n\nFootnote *1*: That support was unfortunately already partially broken when\nGit LFS dropped support for Windows 7 and Windows 8, where it now fails\nwith a segmentation fault (or \"Access Violation\" in Windows speak) and\nonly prints a cryptic error message instead. For full details, see\nhttps://github.com/git-for-windows/git/issues/4996. You may note that this\nbreakage was accepted and not reverted by the Git LFS team, citing\nsecurity concerns ;-)\n\nSo you could argue that Git for Windows is already somewhat broken for\nWindows versions prior to Windows 10, but that's not because of a\ncarefully planned roadmap but instead due to forces that are outside my\ncontrol.\n"}]}