Subject: [PATCH v4 0/4] More ISO-8601 support

From: Đoàn Trần Công Danh <[email-masked]>
Date: Thu, 23 Apr 2020 20:52:37 +0700

This series aims to extend support for ISO-8601 datetime format
to allow compact version, and fractional part of ISO-8601.

Changes in v4 from v3:
* s/is_date/set_date/ the function's name suggest it only does validation,
  but it does more than that. Junio suggested to me to use it for validation,
  When I looked more into it, I think it's better to not use it, and rename
  the function to reduce the confusion
* Extract the validate and set time to its own function
* Correct a check for time in compact ISO-8601

Changes in v3 from v2:
* Add example for fractional parts of second in documentation
* Add/Fix regression test on 12:34:56.7.days.ago

Đoàn Trần Công Danh (4):
  date.c: s/is_date/set_date/
  date.c: validate and set time in a helper function
  date.c: skip fractional second part of ISO-8601
  date.c: allow compact version of ISO-8601 datetime

 Documentation/date-formats.txt |  5 ++-
 date.c                         | 66 +++++++++++++++++++++++++---------
 t/t0006-date.sh                |  6 ++++
 3 files changed, 59 insertions(+), 18 deletions(-)

Range-diff against v3:
-:  ---------- > 1:  1fe69008fc date.c: s/is_date/set_date/
-:  ---------- > 2:  0d0e4d8edc date.c: validate and set time in a helper function
1:  c6d42785bb ! 3:  8b18d0ee5d date.c: skip fractional second part of ISO-8601
    @@ Commit message
     
                 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 <[email-masked]>
         Signed-off-by: Đoàn Trần Công Danh <[email-masked]>
     
    @@ date.c: static int match_multi_number(timestamp_t num, char c, const char *date,
     +		if (num3 < 0) {
      			num3 = 0;
     +		} else if (*end == '.' && isdigit(end[1]) &&
    -+			   tm->tm_year != -1 && tm->tm_mon != -1 &&
    -+			   tm->tm_mday != -1) {
    -+			/* Attempt to guess meaning of <num> in HHMMSS.<num>
    -+			 * only interpret as fractional when %Y %m %d is known.
    -+			 */
    ++			   tm->tm_year != -1 && tm->tm_mon != -1 && tm->tm_mday != -1 &&
    ++			   set_time(num, num2, num3, tm) == 0) {
    ++			/* %Y%m%d is known, ignore fractional <num4> in HHMMSS.<num4> */
     +			strtol(end + 1, &end, 10);
     +		}
    - 		if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) {
    - 			tm->tm_hour = num;
    - 			tm->tm_min = num2;
    + 		if (set_time(num, num2, num3, tm) == 0)
    + 			break;
    + 		return 0;
     
      ## t/t0006-date.sh ##
     @@ t/t0006-date.sh: check_parse 2008-02 bad
2:  225b6401bd ! 4:  2812439a26 date.c: allow compact version of ISO-8601 datetime
    @@ date.c: static int match_digit(const char *date, struct tm *tm, int *offset, int
     +		unsigned int num1 = num / 10000;
     +		unsigned int num2 = (num % 10000) / 100;
     +		unsigned int num3 = num % 100;
    -+		if (n == 8 && num1 > 1900 &&
    -+		    num2 > 0 && num2 <= 12 &&
    -+		    num3 > 0  && num3 <= 31) {
    -+			tm->tm_year = num1 - 1900;
    -+			tm->tm_mon  = num2 - 1;
    -+			tm->tm_mday = num3;
    -+		} else if (n == 6 && num1 < 60 && num2 < 60 && num3 <= 60) {
    -+			tm->tm_hour = num1;
    -+			tm->tm_min  = num2;
    -+			tm->tm_sec  = num3;
    -+			if (*end == '.' && isdigit(end[1]))
    -+				strtoul(end + 1, &end, 10);
    -+		}
    ++		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;
     +	}
     +
-- 
2.26.2.384.g435bf60bd5