From bbeaaa332d6be7c6cca991eb0a12feaf03b70962 Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Thu, 11 Feb 2016 20:00:09 -0500
Subject: [PATCH] date: add '--debug' option

* src/date.c: (main): add '--debug' option, enable debugging
   in gnulib's parse-datetime.y module.
* tests/misc/date.pl: repeat tests with '--debug' enable, ensure
   no regression.
* tests/misc/date-debug.sh: test output of '--debug' option.
* tests/local.mk: add above test.
* NEWS: mention new option.
* doc/coreutils.texi: likewise.
---
 NEWS                     |   5 +++
 doc/coreutils.texi       |   8 +++-
 src/date.c               |  13 +++++-
 tests/local.mk           |   1 +
 tests/misc/date-debug.sh | 102 +++++++++++++++++++++++++++++++++++++++++++++++
 tests/misc/date.pl       |  29 ++++++++++++++
 6 files changed, 155 insertions(+), 3 deletions(-)
 create mode 100755 tests/misc/date-debug.sh

diff --git a/NEWS b/NEWS
index 6018aa9..d9f7292 100644
--- a/NEWS
+++ b/NEWS
@@ -38,6 +38,11 @@ GNU coreutils NEWS                                    -*- outline -*-
    stat -f --format=%T now reports the file system type, and tail -f uses
    polling for "prl_fs", inotify for "m1fs", and attempts inotify for "wslfs".
 
+** New Features
+
+   date now accepts the --debug option, to annotate the parsed date string,
+   display timezone information, and warn about potential misuse.
+
 
 * Noteworthy changes in release 8.25 (2016-01-20) [stable]
 
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 5630201..a9029df 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -521,8 +521,8 @@ Include the version number, machine architecture, input files, and
 any other information needed to reproduce the bug: your input, what you
 expected, what you got, and why it is wrong.
 
-If you have a problem with @command{sort}, try running @samp{sort
---debug}, as it can can often help find and fix problems without
+If you have a problem with @command{sort} or @command{date}, try using the
+@option{--debug} option, as it can can often help find and fix problems without
 having to wait for an answer to a bug report.  If the debug output
 does not suffice to fix the problem on your own, please compress and
 attach it to the rest of your bug report.
@@ -15385,6 +15385,10 @@ date -d "$(LC_TIME=C date)"
 @end example
 @xref{Date input formats}.
 
+@item --debug
+annotate the parsed date, display the effective time zone, and warn about
+potential misuse.
+
 @item -f @var{datefile}
 @itemx --file=@var{datefile}
 @opindex -f
diff --git a/src/date.c b/src/date.c
index e73196b..8819432 100644
--- a/src/date.c
+++ b/src/date.c
@@ -78,7 +78,8 @@ static char const rfc_2822_format[] = "%a, %d %b %Y %H:%M:%S %z";
    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
 enum
 {
-  RFC_3339_OPTION = CHAR_MAX + 1
+  RFC_3339_OPTION = CHAR_MAX + 1,
+  DEBUG_DATE_PARSING
 };
 
 static char const short_options[] = "d:f:I::r:Rs:u";
@@ -86,6 +87,7 @@ static char const short_options[] = "d:f:I::r:Rs:u";
 static struct option const long_options[] =
 {
   {"date", required_argument, NULL, 'd'},
+  {"debug", no_argument, NULL, DEBUG_DATE_PARSING},
   {"file", required_argument, NULL, 'f'},
   {"iso-8601", optional_argument, NULL, 'I'},
   {"reference", required_argument, NULL, 'r'},
@@ -133,6 +135,12 @@ Display the current time in the given FORMAT, or set the system date.\n\
 
       fputs (_("\
   -d, --date=STRING          display time described by STRING, not 'now'\n\
+"), stdout);
+      fputs (_("\
+      --debug                annotate the parsed date,\n\
+                              and warn about questionable usage to stderr\n\
+"), stdout);
+      fputs (_("\
   -f, --file=DATEFILE        like --date; once for each line of DATEFILE\n\
 "), stdout);
       fputs (_("\
@@ -360,6 +368,9 @@ main (int argc, char **argv)
         case 'd':
           datestr = optarg;
           break;
+        case DEBUG_DATE_PARSING:
+          parse_datetime_debug = true;
+          break;
         case 'f':
           batch_file = optarg;
           break;
diff --git a/tests/local.mk b/tests/local.mk
index d3afb6e..43f121c 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -278,6 +278,7 @@ all_tests =					\
   tests/misc/csplit-heap.sh			\
   tests/misc/csplit-io-err.sh			\
   tests/misc/csplit-suppress-matched.pl		\
+  tests/misc/date-debug.sh			\
   tests/misc/date-sec.sh			\
   tests/misc/dircolors.pl			\
   tests/misc/dirname.pl				\
diff --git a/tests/misc/date-debug.sh b/tests/misc/date-debug.sh
new file mode 100755
index 0000000..1f58479
--- /dev/null
+++ b/tests/misc/date-debug.sh
@@ -0,0 +1,102 @@
+#!/bin/sh
+# Test 'date --debug' option
+
+# Copyright (C) 2004-2016 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ date
+
+export LC_ALL=C
+
+remove_trailing_spaces() { sed 's/ *$//'; }
+
+##
+## Test 1: complex date string
+##
+in1='TZ="Asia/Tokyo" Sun, 90-12-11 + 3 days - 90 minutes'
+
+cat<<EOF>exp1
+date: parsed day part: Sun (day ordinal=0 number=0)
+date: parsed date part: (Y-M-D) 0090-12-11
+date: parsed relative part: +3 day(s)
+date: parsed relative part: +3 day(s) -90 minutes
+date: input timezone: +09:00 (set from TZ="Asia/Tokyo" in date string)
+date: warning: adjusting year value 90 to 1990
+date: warning: using midnight as starting time: 00:00:00
+date: warning: day (Sun) ignored when explicit dates are given
+date: starting date/time: '(Y-M-D) 1990-12-11 00:00:00 TZ=+09:00'
+date: warning: when adding relative days, it is recommended to specify 12:00pm
+date: after date adjustment (+0 years, +0 months, +3 days),
+date:     new date/time = '(Y-M-D) 1990-12-14 00:00:00 TZ=+09:00'
+date: '(Y-M-D) 1990-12-14 00:00:00 TZ=+09:00' = 661100400 epoch-seconds
+date: after time adjustment (+0 hours, -90 minutes, +0 seconds, +0 ns),
+date:     new time = 661095000 epoch-seconds
+date: output timezone: -06:00 (set from TZ="America/Belize" environment value)
+date: final: 661095000.000000000 (epoch-seconds)
+date: final: (Y-M-D) 1990-12-13 13:30:00 (UTC0)
+date: final: (Y-M-D) 1990-12-13 07:30:00 (output timezone TZ=-06:00)
+Thu Dec 13 07:30:00 CST 1990
+EOF
+
+TZ=America/Belize date --debug -d "$in1" >out1s 2>&1 || framework_failure_
+remove_trailing_spaces < out1s > out1 || framework_failure_
+
+compare exp1 out1 || fail=1
+
+##
+## Test 2: Invalid date from Coreutils' FAQ
+##         (with explicit timezone added)
+in2='TZ="America/Edmonton" 2006-04-02 02:30:00'
+cat<<EOF>exp2
+date: parsed date part: (Y-M-D) 2006-04-02
+date: parsed time part: 02:30:00
+date: input timezone: -07:00 (set from TZ="America/Edmonton" in date string)
+date: using specified time as starting value: '02:30:00'
+date: error: invalid date/time value:
+date:     user provided time: '(Y-M-D) 2006-04-02 02:30:00 TZ=-07:00'
+date:        normalized time: '(Y-M-D) 2006-04-02 03:30:00 TZ=-07:00'
+date:                                             --
+date:      possible reasons:
+date:        non-existing due to daylight-saving time;
+date:        numeric values overflow;
+date:        missing timezone
+date: invalid date 'TZ="America/Edmonton" 2006-04-02 02:30:00'
+EOF
+
+# date should return 1 (error) for invalid date
+returns_ 1 date --debug -d "$in2" >out2s 2>&1 || framework_failure_
+remove_trailing_spaces < out2s > out2 || framework_failure_
+compare exp2 out2 || fail=1
+
+##
+## Test 3: timespec (input always UTC, output is TZ-dependent)
+##
+in3='@1'
+cat<<EOF>exp3
+date: parsed number of seconds part: number of seconds: 1
+date: input timezone: +00:00 (set from '@timespec' - always UTC0)
+date: output timezone: -05:00 (set from TZ="America/Lima" environment value)
+date: final: 1.000000000 (epoch-seconds)
+date: final: (Y-M-D) 1970-01-01 00:00:01 (UTC0)
+date: final: (Y-M-D) 1969-12-31 19:00:01 (output timezone TZ=-05:00)
+Wed Dec 31 19:00:01 PET 1969
+EOF
+
+TZ=America/Lima date --debug -d "$in3" >out3s 2>&1 || framework_failure_
+remove_trailing_spaces < out3s > out3 || framework_failure_
+compare exp3 out3 || fail=1
+
+Exit $fail
diff --git a/tests/misc/date.pl b/tests/misc/date.pl
index 3454dcc..4449a48 100755
--- a/tests/misc/date.pl
+++ b/tests/misc/date.pl
@@ -314,6 +314,35 @@ foreach my $t (@Tests)
       }
   }
 
+# Repeat all tests with --debug option, ensure it does not cause any regression
+my @debug_tests;
+foreach my $t (@Tests)
+  {
+    # Skip tests with EXIT!=0 or ERR_SUBST part
+    # (as '--debug' requires its own ERR_SUBST).
+    my $exit_val;
+    my $have_err_subst;
+    foreach my $e (@$t)
+      {
+        next unless ref $e && ref $e eq 'HASH';
+        $exit_val = $e->{EXIT} if defined $e->{EXIT};
+        $have_err_subst = 1 if defined $e->{ERR_SUBST};
+      }
+    next if $exit_val || $have_err_subst;
+
+    # Duplicate the test, add '--debug' argument
+    my @newt = @$t;
+    $newt[0] = 'dbg_' . $newt[0];
+    $newt[1] = '--debug ' . $newt[1];
+
+    # Discard all debug printouts before comparing output
+    push @newt, {ERR_SUBST => q!s/^date: .*\n//m!};
+
+    push @debug_tests, \@newt;
+  }
+push @Tests, @debug_tests;
+
+
 my $save_temps = $ENV{DEBUG};
 my $verbose = $ENV{VERBOSE};
 
-- 
2.7.0

