Subject: Re: bug#55401: date man page

From: Pádraig Brady <[email-masked]>
Date: Sat, 14 May 2022 01:42:37 +0100

On 13/05/2022 20:10, t0th wrote:
> Man page of date command should make explicit that -d and -r options are
> mutually exclusive.

Right.  More accurately, we might have a sentence to say that:
"All options to specify the date to display are mutually exclusive.
I.e.: --date, --file, --reference, --resolution".
However...

> date -d -3 minutes -r tmp.txt "+%Y%m%d_%H%M"
> date: the options to specify dates for printing are mutually exclusive

As you've seen, one might expect to be able to combine, as -d can be relative.
So theoretically we could support this (with the attached),
to honor the relative adjustment, but give precedence to a non relative date.

   $ src/date -r src/ls.c -d '-3 minutes'
   Fri 15 Apr 2022 16:30:53 IST
   $ src/date -r src/ls.c -d '1/1/2022'
   Sat 01 Jan 2022 00:00:00 GMT

In fact touch(1) behaves like this, which suggests date(1) should also.
 From the info docs for the touch --reference option:

  "--reference
   Use the times of the reference FILE instead of the current time.
   If this option is combined with the --date=TIME
   (-d TIME) option, the reference FILES's time is
   the origin for any relative TIMEs given, but is otherwise ignored."

BTW, one might also expect that multiple -d options might combine like this,
however currently we silently ignore multiple -d (or -s) options.
The attached also at least warns about this with --debug:

   $ date --debug -d '15/4/2022' -d '-3 minutes'
   date: discarding previous -d: ‘15/4/2022’
   date: parsed relative part: -3 minutes
   ...

cheers,
Pádraig

diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 7bca37b71..ae6ec4def 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -16058,7 +16058,8 @@ is not set.  @xref{TZ Variable,, Specifying the Time Zone with
 @cindex formatting times
 If given an argument that starts with a @samp{+}, @command{date} prints the
 current date and time (or the date and time specified by the
-@option{--date} option, see below) in the format defined by that argument,
+@option{--date}, @option{--file}, or @option{--reference} options, see below)
+in the format defined by that argument,
 which is similar to that of the @code{strftime} function.  Except for
 conversion specifiers, which start with @samp{%}, characters in the
 format string are printed unchanged.  The conversion specifiers are
@@ -16502,6 +16503,9 @@ for the @option{--date} (@option{-d}) and @option{--file}
 @opindex --reference
 Display the date and time of the last modification of @var{file},
 instead of the current date and time.
+If this option is combined with the @option{--date} or @option{--file}
+options, the reference @var{file}'s time is
+the origin for any relative @var{time}s given, but is otherwise ignored.
 
 @item --resolution
 @opindex --resolution
diff --git a/src/date.c b/src/date.c
index 9a282e2f5..de94136fe 100644
--- a/src/date.c
+++ b/src/date.c
@@ -420,6 +420,8 @@ main (int argc, char **argv)
       switch (optc)
         {
         case 'd':
+          if (datestr && (parse_datetime_flags & PARSE_DATETIME_DEBUG))
+            error (0, 0, _("discarding previous -d: %s"), quote (datestr));
           datestr = optarg;
           break;
         case DEBUG_DATE_PARSING_OPTION:
@@ -469,6 +471,8 @@ main (int argc, char **argv)
           new_format = rfc_email_format;
           break;
         case 's':
+          if (set_datestr && (parse_datetime_flags & PARSE_DATETIME_DEBUG))
+            error (0, 0, _("discarding previous -s: %s"), quote (set_datestr));
           set_datestr = optarg;
           set_date = true;
           break;
@@ -494,8 +498,7 @@ main (int argc, char **argv)
         }
     }
 
-  int option_specified_date = (!!datestr + !!batch_file + !!reference
-                               + get_resolution);
+  int option_specified_date = (!!datestr + !!batch_file + get_resolution);
 
   if (option_specified_date > 1)
     {
@@ -511,6 +514,9 @@ main (int argc, char **argv)
       usage (EXIT_FAILURE);
     }
 
+  /* Use as base for other "relative" specified dates.  */
+  option_specified_date += !!reference;
+
   if (optind < argc)
     {
       if (optind + 1 < argc)
@@ -597,7 +603,8 @@ main (int argc, char **argv)
                 die (EXIT_FAILURE, errno, "%s", quotef (reference));
               when = get_stat_mtime (&refstats);
             }
-          else if (get_resolution)
+
+          if (get_resolution)
             {
               long int res = gettime_res ();
               when.tv_sec = res / TIMESPEC_HZ;
@@ -605,9 +612,10 @@ main (int argc, char **argv)
             }
           else
             {
+              struct timespec *now = reference ? &when : NULL;
               if (set_datestr)
                 datestr = set_datestr;
-              valid_date = parse_datetime2 (&when, datestr, NULL,
+              valid_date = parse_datetime2 (&when, datestr, now,
                                             parse_datetime_flags,
                                             tz, tzstring);
             }