From c933b28d87062a0c68de298900b9060f577f865a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90o=C3=A0n=20Tr=E1=BA=A7n=20C=C3=B4ng=20Danh?= Date: Thu, 23 Apr 2020 20:52:38 +0700 Subject: [PATCH 1/4] date.c: s/is_date/set_date/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function is_date, confusingly also set tm_year. tm_mon, and tm_mday after validating input. Rename to set_date to reflect its real usage. Also, change return value is 0 on success and -1 on failure following our convention on function do some real work. Signed-off-by: Đoàn Trần Công Danh Signed-off-by: Junio C Hamano --- date.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/date.c b/date.c index b0d9a8421d..b67c5abe24 100644 --- a/date.c +++ b/date.c @@ -497,7 +497,7 @@ static int match_alpha(const char *date, struct tm *tm, int *offset) return skip_alpha(date); } -static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm) +static int set_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm) { if (month > 0 && month < 13 && day > 0 && day < 32) { struct tm check = *tm; @@ -518,9 +518,9 @@ static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, else if (year < 38) r->tm_year = year + 100; else - return 0; + return -1; if (!now_tm) - return 1; + return 0; specified = tm_to_time_t(r); @@ -529,14 +529,14 @@ static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, * sure it is not later than ten days from now... */ if ((specified != -1) && (now + 10*24*3600 < specified)) - return 0; + return -1; tm->tm_mon = r->tm_mon; tm->tm_mday = r->tm_mday; if (year != -1) tm->tm_year = r->tm_year; - return 1; + return 0; } - return 0; + return -1; } static int match_multi_number(timestamp_t num, char c, const char *date, @@ -575,10 +575,10 @@ static int match_multi_number(timestamp_t num, char c, const char *date, if (num > 70) { /* yyyy-mm-dd? */ - if (is_date(num, num2, num3, NULL, now, tm)) + if (set_date(num, num2, num3, NULL, now, tm) == 0) break; /* yyyy-dd-mm? */ - if (is_date(num, num3, num2, NULL, now, tm)) + if (set_date(num, num3, num2, NULL, now, tm) == 0) break; } /* Our eastern European friends say dd.mm.yy[yy] @@ -586,14 +586,14 @@ static int match_multi_number(timestamp_t num, char c, const char *date, * mm/dd/yy[yy] form only when separator is not '.' */ if (c != '.' && - is_date(num3, num, num2, refuse_future, now, tm)) + set_date(num3, num, num2, refuse_future, now, tm) == 0) break; /* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */ - if (is_date(num3, num2, num, refuse_future, now, tm)) + if (set_date(num3, num2, num, refuse_future, now, tm) == 0) break; /* Funny European mm.dd.yy */ if (c == '.' && - is_date(num3, num, num2, refuse_future, now, tm)) + set_date(num3, num, num2, refuse_future, now, tm) == 0) break; return 0; } From 4f89f4fc9ac494fde3f3bede19a7599f77afe8dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90o=C3=A0n=20Tr=E1=BA=A7n=20C=C3=B4ng=20Danh?= Date: Fri, 24 Apr 2020 22:07:30 +0700 Subject: [PATCH 2/4] date.c: validate and set time in a helper function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In a later patch, we will reuse this logic, move it to a helper, now. While we're at it, explicit states that we intentionally ignore old-and-defective 2nd leap second. Signed-off-by: Đoàn Trần Công Danh Signed-off-by: Junio C Hamano --- date.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/date.c b/date.c index b67c5abe24..fa39e5e8a5 100644 --- a/date.c +++ b/date.c @@ -539,6 +539,20 @@ static int set_date(int year, int month, int day, struct tm *now_tm, time_t now, return -1; } +static int set_time(long hour, long minute, long second, struct tm *tm) +{ + /* We accept 61st second because of leap second */ + if (0 <= hour && hour <= 24 && + 0 <= minute && minute < 60 && + 0 <= second && second <= 60) { + tm->tm_hour = hour; + tm->tm_min = minute; + tm->tm_sec = second; + return 0; + } + return -1; +} + static int match_multi_number(timestamp_t num, char c, const char *date, char *end, struct tm *tm, time_t now) { @@ -556,10 +570,7 @@ static int match_multi_number(timestamp_t num, char c, const char *date, case ':': if (num3 < 0) num3 = 0; - if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) { - tm->tm_hour = num; - tm->tm_min = num2; - tm->tm_sec = num3; + if (set_time(num, num2, num3, tm) == 0) { break; } return 0; From b784840ca84c708708d1ab0b872eb3a6fb3200b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90o=C3=A0n=20Tr=E1=BA=A7n=20C=C3=B4ng=20Danh?= Date: Fri, 24 Apr 2020 22:07:31 +0700 Subject: [PATCH 3/4] date.c: skip fractional second part of ISO-8601 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-commit(1) says ISO-8601 is one of our supported date format. ISO-8601 allows timestamps to have a fractional number of seconds. We represent time only in terms of whole seconds, so we never bothered parsing fractional seconds. However, it's better for us to parse and throw away the fractional part than to refuse to parse the timestamp at all. And refusing parsing fractional second part may confuse the parse to think fractional and timezone as day and month in this example: 2008-02-14 20:30:45.019-04:00 While doing this, make sure that we only interpret the number after the second and the dot as fractional when and only when the date is known, since only ISO-8601 allows the fractional part, and we've taught our users to interpret "12:34:56.7.days.ago" as a way to specify a time relative to current time. Reported-by: Brian M. Carlson Helped-by: Junio C Hamano Signed-off-by: Đoàn Trần Công Danh Signed-off-by: Junio C Hamano --- Documentation/date-formats.txt | 5 ++++- date.c | 12 ++++++++++++ t/t0006-date.sh | 3 +++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Documentation/date-formats.txt b/Documentation/date-formats.txt index 6926e0a4c8..7e7eaba643 100644 --- a/Documentation/date-formats.txt +++ b/Documentation/date-formats.txt @@ -20,7 +20,10 @@ RFC 2822:: ISO 8601:: Time and date specified by the ISO 8601 standard, for example `2005-04-07T22:13:13`. The parser accepts a space instead of the - `T` character as well. + `T` character as well. Fractional parts of a second will be ignored, + for example `2005-04-07T22:13:13.019` will be treated as + `2005-04-07T22:13:13` + + NOTE: In addition, the date part is accepted in the following formats: `YYYY.MM.DD`, `MM/DD/YYYY` and `DD.MM.YYYY`. diff --git a/date.c b/date.c index fa39e5e8a5..2c9071d53f 100644 --- a/date.c +++ b/date.c @@ -553,6 +553,11 @@ static int set_time(long hour, long minute, long second, struct tm *tm) return -1; } +static int is_date_known(struct tm *tm) +{ + return tm->tm_year != -1 && tm->tm_mon != -1 && tm->tm_mday != -1; +} + static int match_multi_number(timestamp_t num, char c, const char *date, char *end, struct tm *tm, time_t now) { @@ -571,6 +576,13 @@ static int match_multi_number(timestamp_t num, char c, const char *date, if (num3 < 0) num3 = 0; if (set_time(num, num2, num3, tm) == 0) { + /* + * If %H:%M:%S was just parsed followed by: . + * Consider (& discard) it as fractional second + * if %Y%m%d is parsed before. + */ + if (*end == '.' && isdigit(end[1]) && is_date_known(tm)) + strtol(end + 1, &end, 10); break; } return 0; diff --git a/t/t0006-date.sh b/t/t0006-date.sh index d9fcc829a9..80917c81c3 100755 --- a/t/t0006-date.sh +++ b/t/t0006-date.sh @@ -81,6 +81,8 @@ check_parse 2008-02 bad check_parse 2008-02-14 bad check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 +0000' check_parse '2008-02-14 20:30:45 -0500' '2008-02-14 20:30:45 -0500' +check_parse '2008.02.14 20:30:45 -0500' '2008-02-14 20:30:45 -0500' +check_parse '2008-02-14 20:30:45.019-04:00' '2008-02-14 20:30:45 -0400' check_parse '2008-02-14 20:30:45 -0015' '2008-02-14 20:30:45 -0015' check_parse '2008-02-14 20:30:45 -5' '2008-02-14 20:30:45 +0000' check_parse '2008-02-14 20:30:45 -5:' '2008-02-14 20:30:45 +0000' @@ -103,6 +105,7 @@ check_approxidate 5.seconds.ago '2009-08-30 19:19:55' check_approxidate 10.minutes.ago '2009-08-30 19:10:00' check_approxidate yesterday '2009-08-29 19:20:00' check_approxidate 3.days.ago '2009-08-27 19:20:00' +check_approxidate '12:34:56.3.days.ago' '2009-08-27 12:34:56' check_approxidate 3.weeks.ago '2009-08-09 19:20:00' check_approxidate 3.months.ago '2009-05-30 19:20:00' check_approxidate 2.years.3.months.ago '2007-05-30 19:20:00' From 544ed961a50c3442a3ec5643f2c443cd3e17311a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90o=C3=A0n=20Tr=E1=BA=A7n=20C=C3=B4ng=20Danh?= Date: Fri, 24 Apr 2020 22:07:32 +0700 Subject: [PATCH 4/4] date.c: allow compact version of ISO-8601 datetime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Đoàn Trần Công Danh Signed-off-by: Junio C Hamano --- date.c | 14 ++++++++++++++ t/t0006-date.sh | 3 +++ 2 files changed, 17 insertions(+) diff --git a/date.c b/date.c index 2c9071d53f..f9ea807b3a 100644 --- a/date.c +++ b/date.c @@ -687,6 +687,20 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt n++; } while (isdigit(date[n])); + /* 8 digits, compact style of ISO-8601's date: YYYYmmDD */ + /* 6 digits, compact style of ISO-8601's time: HHMMSS */ + if (n == 8 || n == 6) { + unsigned int num1 = num / 10000; + unsigned int num2 = (num % 10000) / 100; + unsigned int num3 = num % 100; + if (n == 8) + set_date(num1, num2, num3, NULL, time(NULL), tm); + else if (n == 6 && set_time(num1, num2, num3, tm) == 0 && + *end == '.' && isdigit(end[1])) + strtoul(end + 1, &end, 10); + return end - date; + } + /* Four-digit year or a timezone? */ if (n == 4) { if (num <= 1400 && *offset == -1) { diff --git a/t/t0006-date.sh b/t/t0006-date.sh index 80917c81c3..75ee9a96b8 100755 --- a/t/t0006-date.sh +++ b/t/t0006-date.sh @@ -82,6 +82,9 @@ check_parse 2008-02-14 bad check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 +0000' check_parse '2008-02-14 20:30:45 -0500' '2008-02-14 20:30:45 -0500' check_parse '2008.02.14 20:30:45 -0500' '2008-02-14 20:30:45 -0500' +check_parse '20080214T203045-04:00' '2008-02-14 20:30:45 -0400' +check_parse '20080214T203045 -04:00' '2008-02-14 20:30:45 -0400' +check_parse '20080214T203045.019-04:00' '2008-02-14 20:30:45 -0400' check_parse '2008-02-14 20:30:45.019-04:00' '2008-02-14 20:30:45 -0400' check_parse '2008-02-14 20:30:45 -0015' '2008-02-14 20:30:45 -0015' check_parse '2008-02-14 20:30:45 -5' '2008-02-14 20:30:45 +0000'