From f4b67a5b3f6a54bb69ad0e7ee23c25fd37ce50cd Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Thu, 11 Feb 2016 19:49:24 -0500
Subject: [PATCH 1/2] parse-datetime: add optional debug printing

print parsing information, warnings, and errors to stderr.

* lib/parse-datetime.h: (parse_datetimte_debug): new global variable.
* lib/parse-datetime.y:
    (DEBUG_*):  macros calling debug functions if debugging is enabled.
    (PROGRESS*): same as DEBUG, for progress reporting.
    (dbg_printf): print message to stderr, with 'date' prefix.
    (struct parser_control): add 'debug_*_seen' variables.
    (str_days): converts day ordinal/number to string (e.g. 'last wed').
    (debug_print_current_time, debug_print_relateive_time): prints the
    current/relative date/time value of parser_control.
    (YACC parser syntax): print parsed parts with DEBUG_* macros.
    (to_year): warn about 2-digit year parsing.
    (yylex):   warn about unrecognized words.
    (get_effective_timezone): returns current timezone in minutes.
    (debug_strf{time,date,datetime}): convert 'struct tm' to string as
    clearly and unambigiously as possible.
    (debug_mktime_not_ok): print detailed information about failed
    date/time values.
    (parse_datetime): add DEBUG messages for failures, warnings. Add
    PROGRESS messages for status messages.
* modules/parse-datetime: add 'timegm' dependency.
---
 lib/parse-datetime.h   |   2 +
 lib/parse-datetime.y   | 674 +++++++++++++++++++++++++++++++++++++++++++++++--
 modules/parse-datetime |   1 +
 3 files changed, 657 insertions(+), 20 deletions(-)

diff --git a/lib/parse-datetime.h b/lib/parse-datetime.h
index c38b000..b849db3 100644
--- a/lib/parse-datetime.h
+++ b/lib/parse-datetime.h
@@ -19,4 +19,6 @@
 #include <stdbool.h>
 #include <time.h>
 
+extern bool parse_datetime_debug;
+
 bool parse_datetime (struct timespec *, char const *, struct timespec const *);
diff --git a/lib/parse-datetime.y b/lib/parse-datetime.y
index 163a0ea..232c203 100644
--- a/lib/parse-datetime.y
+++ b/lib/parse-datetime.y
@@ -59,6 +59,7 @@
 # undef static
 #endif
 
+#include <inttypes.h>
 #include <c-ctype.h>
 #include <limits.h>
 #include <stdio.h>
@@ -105,6 +106,8 @@
 
 #define HOUR(x) ((x) * 60)
 
+#define STREQ(a, b) (strcmp (a, b) == 0)
+
 /* long_time_t is a signed integer type that contains all time_t values.  */
 verify (TYPE_IS_INTEGER (time_t));
 #if TIME_T_FITS_IN_LONG_INT
@@ -118,6 +121,64 @@ typedef time_t long_time_t;
    errors that the cast doesn't.  */
 static unsigned char to_uchar (char ch) { return ch; }
 
+/* Enable diagnostic debug output to STDERR */
+bool parse_datetime_debug = false;
+
+/* Debug message without parameters */
+#define DEBUG0(msg)                             \
+  do {                                          \
+    if (parse_datetime_debug)                   \
+      dbg_printf (msg);                         \
+  } while (0)
+
+/* Debug message with printf-style parameters */
+#define DEBUG(x,...)                            \
+  do {                                          \
+    if (parse_datetime_debug)                   \
+      dbg_printf (x,__VA_ARGS__);               \
+  } while (0)
+
+/* Progress messages treated the same as debug messages */
+#define PROGRESS  DEBUG
+#define PROGRESS0 DEBUG0
+
+/* Print the date/time of the parser_control struct */
+#define DEBUG_PRINT_CURRENT_TIME(item,pc)  \
+  do {                                     \
+    if (parse_datetime_debug)              \
+      debug_print_current_time (item, pc); \
+  } while (0)
+
+/* Print the relative date/time of the parser control struct */
+#define DEBUG_PRINT_RELATIVE_TIME(item,pc) \
+  do {                                     \
+    if (parse_datetime_debug)              \
+      debug_print_rel_time (item, pc);     \
+  } while (0)
+
+/* if 'mktime_ok' failed, print informative message with possible reasons */
+#define DEBUG_MKTIME_NOT_OK(tm0,tm1,pc,zones_seen)      \
+  do {                                                  \
+    if (parse_datetime_debug)                           \
+      debug_mktime_not_ok (tm0, tm1, pc, zones_seen);   \
+   } while (0)
+
+
+
+static void
+dbg_printf (const char *msg,...)
+{
+  va_list args;
+  /* TODO: use gnulib's 'program_name' instead? */
+  fputs ("date: ", stderr);
+
+  va_start (args, msg);
+  vfprintf (stderr, msg, args);
+  va_end (args);
+}
+
+
+
 /* Lots of this code assumes time_t and time_t-like values fit into
    long_time_t.  */
 verify (TYPE_MINIMUM (long_time_t) <= TYPE_MINIMUM (time_t)
@@ -209,6 +270,20 @@ typedef struct
   size_t times_seen;
   size_t zones_seen;
 
+  /* which of the 'seen' parts has been printed when debugging */
+  size_t debug_dates_seen;
+  size_t debug_days_seen;
+  size_t debug_local_zones_seen;
+  size_t debug_dsts_seen;
+  size_t debug_times_seen;
+  size_t debug_zones_seen;
+
+  /* true if the user specified explicit ordinal day value, */
+  bool debug_ordinal_day_seen;
+
+  /* the default input timezone, set by TZ value */
+  long int debug_default_input_timezone;
+
   /* Table of local time zone abbreviations, terminated by a null entry.  */
   table local_time_zone_table[3];
 } parser_control;
@@ -282,6 +357,174 @@ set_hhmmss (parser_control *pc, long int hour, long int minutes,
   pc->seconds.tv_nsec = nsec;
 }
 
+/* returns a textual representation of the day ordinal/number values
+   in the parser_control struct (e.g. 'last wed', 'this tues', 'thu') */
+static const char*
+str_days (parser_control *pc, char* /*output*/ buffer, size_t n)
+{
+  /* TODO: use the  relative_time_table[] for reverse lookup */
+  static const char* ordinal_values[] = {
+     "last",
+     "this",
+     "next/first",
+     "(SECOND)", /* SECOND is commented out in relative_time_table[] */
+     "third",
+     "fourth",
+     "fifth",
+     "sixth",
+     "seventh",
+     "eight",
+     "ninth",
+     "tenth",
+     "eleventh",
+     "twelfth"};
+
+  static const char* days_values[] = {
+     "Sun",
+     "Mon",
+     "Tue",
+     "Wed",
+     "Thu",
+     "Fri",
+     "Sat"
+    };
+
+  /* don't add an ordinal prefix if the user didn't specify it
+     (e.g., "this wed" vs "wed") */
+  if (pc->debug_ordinal_day_seen)
+    {
+      /* use word description of possible (e.g. -1 = last, 3 = third) */
+      if (pc->day_ordinal>=-1 && pc->day_ordinal <=12)
+        {
+          strncpy (buffer, ordinal_values[ pc->day_ordinal+1 ], n);
+          buffer[n-1]='\0';
+        }
+      else
+        {
+          snprintf (buffer,n,"%ld",pc->day_ordinal);
+        }
+    }
+  else
+    {
+      buffer[0] = '\0';
+    }
+
+  /* Add the day name */
+  if (pc->day_number>=0 && pc->day_number<=6)
+    {
+      size_t l = strlen (buffer);
+      if (l>0)
+        {
+          strncat (buffer," ",n-l);
+          ++l;
+        }
+      strncat (buffer,days_values[pc->day_number],n-l);
+    }
+  else
+    {
+      /* invalid day_number value - should never happen */
+    }
+  return buffer;
+}
+
+/* debugging: print the current time in the parser_control structure.
+   The parser will increment "*_seen" members for those which were parsed.
+   This function will print only newly seen parts. */
+static void
+debug_print_current_time (const char* item, parser_control *pc)
+{
+  char tmp[100] = {0};
+  DEBUG ("parsed %s part: ", item); /* no newline, more items printed below */
+
+  if (pc->dates_seen != pc->debug_dates_seen)
+    {
+      /*TODO: use pc->year.negative? */
+      fprintf (stderr,"(Y-M-D) %04ld-%02ld-%02ld ",
+              pc->year.value, pc->month, pc->day);
+      pc->debug_dates_seen = pc->dates_seen;
+    }
+
+  if (pc->times_seen != pc->debug_times_seen)
+    {
+      fprintf (stderr,"%02ld:%02ld:%02ld",
+              pc->hour, pc->minutes, pc->seconds.tv_sec);
+      if (pc->seconds.tv_nsec!=0)
+        fprintf (stderr,"%09ld", pc->seconds.tv_nsec);
+      if (pc->meridian==MERpm)
+        fputs ("pm",stderr);
+      fputc (' ',stderr);
+      pc->debug_times_seen = pc->times_seen;
+    }
+
+  if (pc->days_seen != pc->debug_days_seen)
+    {
+      fprintf (stderr,"%s (day ordinal=%ld number=%d)",
+              str_days (pc,tmp,sizeof (tmp)),
+              pc->day_ordinal, pc->day_number);
+      pc->debug_days_seen = pc->days_seen ;
+    }
+
+  if (pc->dsts_seen != pc->debug_dsts_seen)
+    {
+      fprintf (stderr,"is-dst=%d ", pc->local_isdst);
+      pc->dsts_seen = pc->debug_dsts_seen;
+    }
+
+  /* TODO: fix incorrect display of EST=2:08h? */
+  if (pc->zones_seen != pc->debug_zones_seen)
+    {
+      fprintf (stderr,"TZ=%+03d:%02d", (int)(pc->time_zone/60),
+              abs ((int)pc->time_zone%60));
+      pc->debug_zones_seen = pc->zones_seen;
+    }
+
+  if (pc->local_zones_seen != pc->debug_local_zones_seen)
+    {
+      fprintf (stderr,"Local-TZ=%+03d:%02d", (int)(pc->time_zone/60),
+              abs ((int)pc->time_zone%60));
+      pc->debug_local_zones_seen = pc->local_zones_seen;
+    }
+
+  if (pc->timespec_seen)
+    fprintf (stderr,"number of seconds: %ld", pc->seconds.tv_sec);
+
+  fputc ('\n', stderr);
+}
+
+/* debugging: print the current relative values. */
+static void
+debug_print_rel_time (const char* item, const parser_control *pc)
+{
+  DEBUG ("parsed %s part: ", item); /* no newline, more items printed below */
+
+  if (pc->rel.year==0 && pc->rel.month==0 && pc->rel.day==0
+      && pc->rel.hour==0 && pc->rel.minutes==00 && pc->rel.seconds == 0
+      && pc->rel.ns==0)
+    {
+      /* Special case: relative time of this/today/now */
+      fputs ("today/this/now\n",stderr);
+      return ;
+    }
+
+#define PRINT_REL_PART(x,name)                          \
+  do {                                                  \
+    if ( (pc->rel.x) != 0 )                             \
+      fprintf (stderr,"%+ld %s ", pc->rel.x, name);      \
+  } while (0)
+
+  PRINT_REL_PART (year,"year(s)");
+  PRINT_REL_PART (month,"month(s)");
+  PRINT_REL_PART (day,"day(s)");
+  PRINT_REL_PART (hour,"hour(s)");
+  PRINT_REL_PART (minutes,"minutes");
+  PRINT_REL_PART (seconds,"seconds");
+  PRINT_REL_PART (ns,"nanoseconds");
+
+  fputc ('\n',stderr);
+}
+
+
+
 %}
 
 /* We want a reentrant parser, even if the TZ manipulation and the calls to
@@ -330,6 +573,7 @@ timespec:
       {
         pc->seconds = $2;
         pc->timespec_seen = true;
+        DEBUG_PRINT_CURRENT_TIME ("number of seconds", pc);
       }
   ;
 
@@ -340,20 +584,47 @@ items:
 
 item:
     datetime
-      { pc->times_seen++; pc->dates_seen++; }
+      {
+        pc->times_seen++; pc->dates_seen++;
+        DEBUG_PRINT_CURRENT_TIME ("datetime", pc);
+      }
   | time
-      { pc->times_seen++; }
+      {
+        pc->times_seen++;
+        DEBUG_PRINT_CURRENT_TIME ("time", pc);
+      }
   | local_zone
-      { pc->local_zones_seen++; }
+      {
+        pc->local_zones_seen++;
+        DEBUG_PRINT_CURRENT_TIME ("local_zone", pc);
+      }
   | zone
-      { pc->zones_seen++; }
+      {
+        pc->zones_seen++;
+        DEBUG_PRINT_CURRENT_TIME ("zone", pc);
+      }
   | date
-      { pc->dates_seen++; }
+      {
+        pc->dates_seen++;
+        DEBUG_PRINT_CURRENT_TIME ("date", pc);
+      }
   | day
-      { pc->days_seen++; }
+      {
+        pc->days_seen++;
+        DEBUG_PRINT_CURRENT_TIME ("day", pc);
+      }
   | rel
+      {
+        DEBUG_PRINT_RELATIVE_TIME ("relative", pc);
+      }
   | number
+      {
+        DEBUG_PRINT_RELATIVE_TIME ("number", pc);
+      }
   | hybrid
+      {
+        DEBUG_PRINT_RELATIVE_TIME ("hybrid", pc);
+      }
   ;
 
 datetime:
@@ -463,11 +734,13 @@ day:
       {
         pc->day_ordinal = $1;
         pc->day_number = $2;
+        pc->debug_ordinal_day_seen = true;
       }
   | tUNUMBER tDAY
       {
         pc->day_ordinal = $1.value;
         pc->day_number = $2;
+        pc->debug_ordinal_day_seen = true;
       }
   ;
 
@@ -486,12 +759,18 @@ date:
            you want portability, use the ISO 8601 format.  */
         if (4 <= $1.digits)
           {
+            DEBUG ("warning: value %ld has %"PRIuMAX" digits. " \
+                   "Assuming YYYY/MM/DD\n", $1.value, $1.digits);
+
             pc->year = $1;
             pc->month = $3.value;
             pc->day = $5.value;
           }
         else
           {
+            DEBUG("warning: value %ld has less than 4 digits. " \
+                  "Assuming MM/DD/YY[YY]\n", $1.value);
+
             pc->month = $1.value;
             pc->day = $3.value;
             pc->year = $5;
@@ -905,7 +1184,11 @@ to_year (textint textyear)
   /* XPG4 suggests that years 00-68 map to 2000-2068, and
      years 69-99 map to 1969-1999.  */
   else if (textyear.digits == 2)
-    year += year < 69 ? 2000 : 1900;
+    {
+      year += year < 69 ? 2000 : 1900;
+      DEBUG ("warning: adjusting year value %ld to %ld\n",
+             textyear.value, year);
+    }
 
   return year;
 }
@@ -1170,7 +1453,10 @@ yylex (union YYSTYPE *lvalp, parser_control *pc)
           *p = '\0';
           tp = lookup_word (pc, buff);
           if (! tp)
-            return '?';
+            {
+              DEBUG ("error: unknown word '%s'\n", buff);
+              return '?';
+            }
           lvalp->intval = tp->value;
           return tp->type;
         }
@@ -1230,6 +1516,10 @@ mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
    Use heap allocation if TZ's length exceeds this.  */
 enum { TZBUFSIZE = 100 };
 
+/* A reasonable upper bound for the buffer used in debug print outs.
+   see 'days_to_name()' and 'debug_strftime()' */
+enum { DBGBUFSIZE = 50 };
+
 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
    otherwise.  */
 static char *
@@ -1246,6 +1536,129 @@ get_tz (char tzbuf[TZBUFSIZE])
   return tz;
 }
 
+/* debugging: format a 'struct tm' into a buffer, taking the parser's
+   timezone information into account (if pc!=NULL). */
+static const char*
+debug_strfdatetime (const struct tm *tm, const parser_control *pc,
+                    char* /*output*/ buf, size_t n)
+{
+  /* TODO:
+     1. find an optimal way to print date string in a clear and unambiguous
+        format. Currently, always add '(Y-M-D)' prefix.
+        Consider '2016y01m10d'  or 'year(2016) month(01) day(10)'.
+
+        If the user needs debug printing, it means he/she already having
+        issues with the parsing - better to avoid formats that could
+        be mis-interpreted (e.g. just YYYY-MM-DD).
+
+     2. Can strftime be used instead?
+        depends if it is portable and can print invalid dates on all systems.
+
+     3. Print timezone information ?
+
+     4. Print DST information ?
+
+     5. Print nanosecond information ?
+
+     NOTE:
+     Printed date/time values might not be valid, e.g. '2016-02-31'
+     or '2016-19-2016' .  These are the values as parsed from the user
+     string, before validation.
+  */
+  int m = snprintf (buf,n,"(Y-M-D) %04d-%02d-%02d %02d:%02d:%02d",
+                    tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
+                    tm->tm_hour, tm->tm_min, tm->tm_sec);
+
+  /* if parser_control information was provided (for timezone),
+     and there's enough space in the buffer - add timezone info */
+  if (pc != NULL && ((n-m)>0))
+    {
+      const long int tz = (pc->zones_seen || pc->local_zones_seen)
+                          ? pc->time_zone
+                          : pc->debug_default_input_timezone;
+      snprintf (&buf[m],n-m," TZ=%+03d:%02d", (int)(tz/60), abs ((int)tz)%60);
+    }
+  return buf;
+}
+
+static const char*
+debug_strfdate (const struct tm *tm, char* /*output*/ buf, size_t n)
+{
+  snprintf (buf,n,"(Y-M-D) %04d-%02d-%02d",
+            tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday);
+  return buf;
+}
+
+static const char*
+debug_strftime (const struct tm *tm, char* /*output*/ buf, size_t n)
+{
+  snprintf (buf,n,"%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
+  return buf;
+}
+
+/* If 'mktime_ok()' failed, display the failed time values,
+   and provide possible hints. Example output:
+
+    date: error: invalid date/time value:
+    date:     user provided time: '(Y-M-D) 2006-04-02 02:45:00'
+    date:        normalized time: '(Y-M-D) 2006-04-02 03:45:00'
+    date:                                             __
+    date:      possible reasons:
+    date:        non-existing due to daylight-saving time;
+    date:        numeric values overflow;
+    date:        missing timezone;
+ */
+static void
+debug_mktime_not_ok (struct tm const *tm0, struct tm const *tm1,
+                     const parser_control *pc,  bool time_zone_seen)
+{
+  /* TODO: handle t==-1 (as in 'mktime_ok') */
+  char tmp[DBGBUFSIZE];
+
+  const bool eq_sec   = (tm0->tm_sec  == tm1->tm_sec);
+  const bool eq_min   = (tm0->tm_min  == tm1->tm_min);
+  const bool eq_hour  = (tm0->tm_hour == tm1->tm_hour);
+  const bool eq_mday  = (tm0->tm_mday == tm1->tm_mday);
+  const bool eq_month = (tm0->tm_mon  == tm1->tm_mon);
+  const bool eq_year  = (tm0->tm_year == tm1->tm_year);
+
+  const bool dst_shift = eq_sec && eq_min && !eq_hour
+                         && eq_mday && eq_month && eq_year;
+
+  DEBUG0 ("error: invalid date/time value:\n");
+  DEBUG ("    user provided time: '%s'\n",
+        debug_strfdatetime (tm0, pc, tmp, sizeof (tmp)));
+  DEBUG ("       normalized time: '%s'\n",
+        debug_strfdatetime (tm1, pc, tmp, sizeof (tmp)));
+  /* NOTE: the format must be aligned with  debug_strfdatetime() */
+  DEBUG ("                                 %4s %2s %2s %2s %2s %2s\n",
+         eq_year?"":"----", eq_month?"":"--", eq_mday?"":"--",
+         eq_hour?"":"--", eq_min?"":"--", eq_sec?"":"--");
+
+  DEBUG0 ("     possible reasons:\n");
+  if (dst_shift)
+    DEBUG0 ("       non-existing due to daylight-saving time;\n");
+  if (!eq_mday && !eq_month)
+    DEBUG0 ("       invalid day/month combination;\n");
+  DEBUG0 ("       numeric values overflow;\n");
+  DEBUG ("       %s timezone;\n", time_zone_seen?"incorrect":"missing");
+}
+
+
+/* Returns the effective local timezone, in minutes. */
+static long int
+get_effective_timezone (void)
+{
+  /* TODO: check for failures */
+  const time_t z = 0;
+  time_t lz ;
+  struct tm *ltm;
+  ltm = localtime (&z);
+  lz = timegm (ltm)/60;
+  return (long int)lz;
+}
+
+
 /* Parse a date/time string, storing the resulting time value into *RESULT.
    The string itself is pointed to by P.  Return true if successful.
    P can be an incomplete or relative time specification; if so, use
@@ -1266,6 +1679,8 @@ parse_datetime (struct timespec *result, char const *p,
   char *tz0 = NULL;
   char tz0buf[TZBUFSIZE];
   bool ok = true;
+  char dbg_ord[DBGBUFSIZE];
+  char dbg_tm[DBGBUFSIZE];
 
   if (! now)
     {
@@ -1351,6 +1766,14 @@ parse_datetime (struct timespec *result, char const *p,
   pc.local_zones_seen = 0;
   pc.dsts_seen = 0;
   pc.zones_seen = 0;
+  pc.debug_dates_seen = 0;
+  pc.debug_days_seen = 0;
+  pc.debug_times_seen = 0;
+  pc.debug_local_zones_seen = 0;
+  pc.debug_dsts_seen = 0;
+  pc.debug_zones_seen = 0;
+  pc.debug_ordinal_day_seen = false;
+  pc.debug_default_input_timezone = 0;
 
 #if HAVE_STRUCT_TM_TM_ZONE
   pc.local_time_zone_table[0].name = tmp->tm_zone;
@@ -1410,8 +1833,60 @@ parse_datetime (struct timespec *result, char const *p,
       pc.local_time_zone_table[1].name = NULL;
     }
 
+  pc.debug_default_input_timezone = get_effective_timezone ();
+
   if (yyparse (&pc) != 0)
-    goto fail;
+    {
+      DEBUG ("error: parsing failed, stopped at '%s'\n", pc.input);
+      goto fail;
+    }
+
+  /* determine effective timezone source */
+  if (parse_datetime_debug)
+    {
+      long int tz = pc.debug_default_input_timezone;
+      const char* tz_env;
+      const char* tz_src;
+
+      if (pc.timespec_seen)
+        {
+          tz = 0 ;
+          tz_src = "'@timespec' - always UTC0" ;
+        }
+      else if (pc.local_zones_seen || pc.zones_seen)
+        {
+          tz = pc.time_zone;
+          tz_src = "parsed date/time string";
+        }
+      else if ((tz_env = getenv("TZ")))
+        {
+          if (tz_was_altered)
+            {
+              snprintf (dbg_tm, sizeof(dbg_tm), "TZ=\"%s\" in date string",
+                        tz_env);
+              tz_src = dbg_tm;
+            }
+          else if (STREQ(tz_env,"UTC0"))
+            {
+              /* Special case: using 'date -u' simply set TZ=UTC0 */
+              tz_src = "TZ=UTC0 environment value or -u";
+            }
+          else
+            {
+              snprintf (dbg_tm, sizeof(dbg_tm), "TZ=\"%s\" environment value",
+                        tz_env);
+              tz_src = dbg_tm;
+            }
+        }
+      else
+        {
+          tz_src = "system default";
+        }
+
+      PROGRESS ("input timezone: %+03d:%02d (set from %s)\n",
+                (int)(tz/60), abs ((int)tz)%60, tz_src);
+
+    }
 
   if (pc.timespec_seen)
     *result = pc.seconds;
@@ -1419,7 +1894,22 @@ parse_datetime (struct timespec *result, char const *p,
     {
       if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
                | (pc.local_zones_seen + pc.zones_seen)))
-        goto fail;
+        {
+          if (parse_datetime_debug)
+            {
+              if (pc.times_seen > 1)
+                DEBUG0 ("error: seen multiple time parts\n");
+              if (pc.dates_seen > 1)
+                DEBUG0 ("error: seen multiple date parts\n");
+              if (pc.days_seen > 1)
+                DEBUG0 ("error: seen multiple days parts\n");
+              if (pc.dsts_seen > 1)
+                DEBUG0 ("error: seen multiple daylight-saving parts\n");
+              if ( (pc.local_zones_seen + pc.zones_seen) > 1)
+                DEBUG0 ("error: seen multiple time-zone parts\n");
+            }
+          goto fail;
+        }
 
       tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
       tm.tm_mon = pc.month - 1;
@@ -1428,14 +1918,24 @@ parse_datetime (struct timespec *result, char const *p,
         {
           tm.tm_hour = to_hour (pc.hour, pc.meridian);
           if (tm.tm_hour < 0)
-            goto fail;
+            {
+              const char* mrd = (pc.meridian==MERam)?"am":
+                                  (pc.meridian==MERpm)?"pm":"";
+              DEBUG ("error: invalid hour %ld%s\n", pc.hour, mrd);
+
+              goto fail;
+            }
           tm.tm_min = pc.minutes;
           tm.tm_sec = pc.seconds.tv_sec;
+          PROGRESS ("using %s time as starting value: '%s'\n",
+                    (pc.times_seen)?"specified":"current",
+                    debug_strftime (&tm,dbg_tm,sizeof (dbg_tm)));
         }
       else
         {
           tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
           pc.seconds.tv_nsec = 0;
+          DEBUG0 ("warning: using midnight as starting time: 00:00:00\n");
         }
 
       /* Let mktime deduce tm_isdst if we have an absolute time stamp.  */
@@ -1454,7 +1954,11 @@ parse_datetime (struct timespec *result, char const *p,
       if (! mktime_ok (&tm0, &tm, Start))
         {
           if (! pc.zones_seen)
-            goto fail;
+            {
+              DEBUG_MKTIME_NOT_OK (&tm0, &tm, &pc, pc.zones_seen);
+
+              goto fail;
+            }
           else
             {
               /* Guard against falsely reporting errors near the time_t
@@ -1478,12 +1982,21 @@ parse_datetime (struct timespec *result, char const *p,
               sprintf (tz1buf, "XXX%s%ld:%02d", &"-"[time_zone < 0],
                        abs_time_zone_hour, abs_time_zone_min);
               if (setenv ("TZ", tz1buf, 1) != 0)
-                goto fail;
+                {
+                  /* TODO: was warn () + print errno? */
+                  DEBUG ("error: setenv('TZ','%s') failed\n", tz1buf);
+
+                  goto fail;
+                }
               tz_was_altered = true;
               tm = tm0;
               Start = mktime (&tm);
               if (! mktime_ok (&tm0, &tm, Start))
-                goto fail;
+                {
+                  DEBUG_MKTIME_NOT_OK (&tm0, &tm, &pc, pc.zones_seen);
+
+                  goto fail;
+                }
             }
         }
 
@@ -1496,19 +2009,58 @@ parse_datetime (struct timespec *result, char const *p,
           tm.tm_isdst = -1;
           Start = mktime (&tm);
           if (Start == (time_t) -1)
-            goto fail;
+            {
+              DEBUG ("error: day '%s' (day ordinal=%ld number=%d) "      \
+                     "resulted in an invalid date: '%s'\n",
+                     str_days (&pc,dbg_ord,sizeof (dbg_ord)),
+                     pc.day_ordinal,pc.day_number,
+                     debug_strfdatetime (&tm, &pc, dbg_tm,sizeof (dbg_tm)));
+
+              goto fail;
+            }
+
+          PROGRESS ("new start date: '%s' is '%s'\n",
+                    str_days (&pc,dbg_ord,sizeof (dbg_ord)),
+                    debug_strfdatetime (&tm, &pc, dbg_tm,sizeof (dbg_tm)));
+
         }
 
+      if (!pc.dates_seen && !pc.days_seen)
+        PROGRESS ("using current date as starting value: '%s'\n",
+                  debug_strfdate (&tm,dbg_tm,sizeof (dbg_tm)));
+
+      if (pc.days_seen && pc.dates_seen)
+        DEBUG ("warning: day (%s) ignored when explicit dates are given\n",
+               str_days (&pc,dbg_ord,sizeof (dbg_ord)));
+
+      PROGRESS ("starting date/time: '%s'\n",
+                debug_strfdatetime (&tm, &pc, dbg_tm,sizeof (dbg_tm)));
+
+
       /* Add relative date.  */
       if (pc.rel.year | pc.rel.month | pc.rel.day)
         {
+          if ((pc.rel.year != 0 || pc.rel.month !=0) && tm.tm_mday==1)
+            DEBUG0 ("warning: when adding relative months/years, "  \
+                    "it is recommended to specify the 15th of the months\n");
+
+
+          if (pc.rel.day != 0 && tm.tm_hour==0)
+            DEBUG0 ("warning: when adding relative days, "  \
+                    "it is recommended to specify 12:00pm\n");
+
           int year = tm.tm_year + pc.rel.year;
           int month = tm.tm_mon + pc.rel.month;
           int day = tm.tm_mday + pc.rel.day;
           if (((year < tm.tm_year) ^ (pc.rel.year < 0))
               | ((month < tm.tm_mon) ^ (pc.rel.month < 0))
               | ((day < tm.tm_mday) ^ (pc.rel.day < 0)))
-            goto fail;
+            {
+              /* TODO: what is the actual error? int-value wrap-around? */
+              DEBUG ("error: %s:%d\n", __FILE__,__LINE__);
+
+              goto fail;
+            }
           tm.tm_year = year;
           tm.tm_mon = month;
           tm.tm_mday = day;
@@ -1518,7 +2070,20 @@ parse_datetime (struct timespec *result, char const *p,
           tm.tm_isdst = tm0.tm_isdst;
           Start = mktime (&tm);
           if (Start == (time_t) -1)
-            goto fail;
+            {
+              DEBUG ("error: adding relative date resulted "  \
+                     "in an invalid date: '%s'\n",
+                     debug_strfdatetime (&tm, &pc, dbg_tm, sizeof (dbg_tm)));
+
+              goto fail;
+            }
+
+          PROGRESS ("after date adjustment "\
+                    "(%+ld years, %+ld months, %+ld days),\n",
+                    pc.rel.year, pc.rel.month, pc.rel.day);
+          PROGRESS ("    new date/time = '%s'\n",
+                    debug_strfdatetime (&tm, &pc, dbg_tm, sizeof (dbg_tm)));
+
         }
 
       /* The only "output" of this if-block is an updated Start value,
@@ -1533,15 +2098,29 @@ parse_datetime (struct timespec *result, char const *p,
           time_t t = Start;
           struct tm const *gmt = gmtime (&t);
           if (! gmt)
-            goto fail;
+            {
+              /* TODO: use 'warn(3)' + print errno ? */
+              DEBUG ("error: gmtime failed for t=%ld\n",t);
+
+              goto fail;
+            }
           delta -= tm_diff (&tm, gmt);
 #endif
           t1 = Start - delta;
           if ((Start < t1) != (delta < 0))
-            goto fail;  /* time_t overflow */
+            {
+              DEBUG ("error: timezone %ld caused time_t overflow\n",
+                     pc.time_zone);
+
+              goto fail;  /* time_t overflow */
+            }
           Start = t1;
         }
 
+      PROGRESS ("'%s' = %ld epoch-seconds\n",
+                debug_strfdatetime (&tm, &pc, dbg_tm, sizeof (dbg_tm)),
+                Start);
+
       /* Add relative hours, minutes, and seconds.  On hosts that support
          leap seconds, ignore the possibility of leap seconds; e.g.,
          "+ 10 minutes" adds 600 seconds, even if one of them is a
@@ -1570,7 +2149,19 @@ parse_datetime (struct timespec *result, char const *p,
             | ((t3 < t2) ^ (d3 < 0))
             | ((t4 < t3) ^ (d4 < 0))
             | (t5 != t4))
-          goto fail;
+            {
+              DEBUG0 (" error: adding relative time caused an overflow\n");
+
+              goto fail;
+            }
+
+          if (pc.rel.hour | pc.rel.minutes | pc.rel.seconds | pc.rel.ns )
+            {
+              PROGRESS ("after time adjustment "                              \
+                    "(%+ld hours, %+ld minutes, %+ld seconds, %+ld ns),\n",
+                    pc.rel.hour,pc.rel.minutes,pc.rel.seconds,pc.rel.ns);
+              PROGRESS ("    new time = %ld epoch-seconds\n",t5);
+            }
 
         result->tv_sec = t5;
         result->tv_nsec = normalized_ns;
@@ -1586,6 +2177,49 @@ parse_datetime (struct timespec *result, char const *p,
     ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
   if (tz0 != tz0buf)
     free (tz0);
+
+  if (ok && parse_datetime_debug)
+    {
+      /* print local timezone AFTER restoring TZ (if tz_was_altered)*/
+      const long int otz = get_effective_timezone ();
+      const char* tz_src;
+      const char* tz_env;
+
+      if ((tz_env = getenv("TZ")))
+        {
+          /* Special case: using 'date -u' simply set TZ=UTC0 */
+          if (STREQ(tz_env,"UTC0"))
+            {
+              tz_src = "TZ=UTC0 envionment value or -u";
+            }
+          else
+            {
+              snprintf (dbg_tm, sizeof(dbg_tm), "TZ=\"%s\" environment value",
+                        tz_env);
+              tz_src = dbg_tm;
+            }
+        }
+      else
+        {
+          tz_src = "system default";
+        }
+
+      PROGRESS ("output timezone: %+03d:%02d (set from %s)\n",
+                (int)(otz/60), abs ((int)otz)%60, tz_src);
+
+
+      PROGRESS ("final: %ld.%09ld (epoch-seconds)\n",
+            result->tv_sec,result->tv_nsec);
+
+      struct tm const *gmt = gmtime (&result->tv_sec);
+      PROGRESS ("final: %s (UTC0)\n",
+                debug_strfdatetime (gmt, NULL, dbg_tm, sizeof (dbg_tm)));
+      struct tm const *lmt = localtime (&result->tv_sec);
+      PROGRESS ("final: %s (output timezone TZ=%+03d:%02d)\n",
+                debug_strfdatetime (lmt, NULL, dbg_tm, sizeof (dbg_tm)),
+                (int)(otz/60), abs ((int)otz)%60);
+    }
+
   return ok;
 }
 
diff --git a/modules/parse-datetime b/modules/parse-datetime
index fb48136..9f5e40a 100644
--- a/modules/parse-datetime
+++ b/modules/parse-datetime
@@ -18,6 +18,7 @@ mktime
 setenv
 unsetenv
 time
+timegm
 verify
 xalloc
 
-- 
2.7.0


From a90d7809cae2103f42a781bbffc4933ac13be36f Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Thu, 11 Feb 2016 19:57:36 -0500
Subject: [PATCH 2/2] parse-datetime: make debug strings translatable

* lib/parse_datetime.y: make debug strings translatable using gettext
   module and _() macro.
* modules/parse-datetime: add 'gettext-h' to dependencies.
---
 lib/parse-datetime.y   | 148 +++++++++++++++++++++++++------------------------
 modules/parse-datetime |   1 +
 2 files changed, 77 insertions(+), 72 deletions(-)

diff --git a/lib/parse-datetime.y b/lib/parse-datetime.y
index 232c203..8a2c846 100644
--- a/lib/parse-datetime.y
+++ b/lib/parse-datetime.y
@@ -66,8 +66,11 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "gettext.h"
 #include "xalloc.h"
 
+#define _(str) gettext (str)
+
 /* Bison's skeleton tests _STDLIB_H, while some stdlib.h headers
    use _STDLIB_H_ as witness.  Map the latter to the one bison uses.  */
 /* FIXME: this is temporary.  Remove when we have a mechanism to ensure
@@ -434,7 +437,7 @@ static void
 debug_print_current_time (const char* item, parser_control *pc)
 {
   char tmp[100] = {0};
-  DEBUG ("parsed %s part: ", item); /* no newline, more items printed below */
+  DEBUG (_("parsed %s part: "), item); /* no newline, more items printed below */
 
   if (pc->dates_seen != pc->debug_dates_seen)
     {
@@ -458,7 +461,7 @@ debug_print_current_time (const char* item, parser_control *pc)
 
   if (pc->days_seen != pc->debug_days_seen)
     {
-      fprintf (stderr,"%s (day ordinal=%ld number=%d)",
+      fprintf (stderr,_("%s (day ordinal=%ld number=%d)"),
               str_days (pc,tmp,sizeof (tmp)),
               pc->day_ordinal, pc->day_number);
       pc->debug_days_seen = pc->days_seen ;
@@ -466,27 +469,27 @@ debug_print_current_time (const char* item, parser_control *pc)
 
   if (pc->dsts_seen != pc->debug_dsts_seen)
     {
-      fprintf (stderr,"is-dst=%d ", pc->local_isdst);
+      fprintf (stderr,_("is-dst=%d "), pc->local_isdst);
       pc->dsts_seen = pc->debug_dsts_seen;
     }
 
   /* TODO: fix incorrect display of EST=2:08h? */
   if (pc->zones_seen != pc->debug_zones_seen)
     {
-      fprintf (stderr,"TZ=%+03d:%02d", (int)(pc->time_zone/60),
+      fprintf (stderr,_("TZ=%+03d:%02d"), (int)(pc->time_zone/60),
               abs ((int)pc->time_zone%60));
       pc->debug_zones_seen = pc->zones_seen;
     }
 
   if (pc->local_zones_seen != pc->debug_local_zones_seen)
     {
-      fprintf (stderr,"Local-TZ=%+03d:%02d", (int)(pc->time_zone/60),
+      fprintf (stderr,_("Local-TZ=%+03d:%02d"), (int)(pc->time_zone/60),
               abs ((int)pc->time_zone%60));
       pc->debug_local_zones_seen = pc->local_zones_seen;
     }
 
   if (pc->timespec_seen)
-    fprintf (stderr,"number of seconds: %ld", pc->seconds.tv_sec);
+    fprintf (stderr,_("number of seconds: %ld"), pc->seconds.tv_sec);
 
   fputc ('\n', stderr);
 }
@@ -495,14 +498,14 @@ debug_print_current_time (const char* item, parser_control *pc)
 static void
 debug_print_rel_time (const char* item, const parser_control *pc)
 {
-  DEBUG ("parsed %s part: ", item); /* no newline, more items printed below */
+  DEBUG (_("parsed %s part: "), item); /* no newline, more items printed below */
 
   if (pc->rel.year==0 && pc->rel.month==0 && pc->rel.day==0
       && pc->rel.hour==0 && pc->rel.minutes==00 && pc->rel.seconds == 0
       && pc->rel.ns==0)
     {
       /* Special case: relative time of this/today/now */
-      fputs ("today/this/now\n",stderr);
+      fputs (_("today/this/now\n"),stderr);
       return ;
     }
 
@@ -573,7 +576,7 @@ timespec:
       {
         pc->seconds = $2;
         pc->timespec_seen = true;
-        DEBUG_PRINT_CURRENT_TIME ("number of seconds", pc);
+        DEBUG_PRINT_CURRENT_TIME (_("number of seconds"), pc);
       }
   ;
 
@@ -586,44 +589,44 @@ item:
     datetime
       {
         pc->times_seen++; pc->dates_seen++;
-        DEBUG_PRINT_CURRENT_TIME ("datetime", pc);
+        DEBUG_PRINT_CURRENT_TIME (_("datetime"), pc);
       }
   | time
       {
         pc->times_seen++;
-        DEBUG_PRINT_CURRENT_TIME ("time", pc);
+        DEBUG_PRINT_CURRENT_TIME (_("time"), pc);
       }
   | local_zone
       {
         pc->local_zones_seen++;
-        DEBUG_PRINT_CURRENT_TIME ("local_zone", pc);
+        DEBUG_PRINT_CURRENT_TIME (_("local_zone"), pc);
       }
   | zone
       {
         pc->zones_seen++;
-        DEBUG_PRINT_CURRENT_TIME ("zone", pc);
+        DEBUG_PRINT_CURRENT_TIME (_("zone"), pc);
       }
   | date
       {
         pc->dates_seen++;
-        DEBUG_PRINT_CURRENT_TIME ("date", pc);
+        DEBUG_PRINT_CURRENT_TIME (_("date"), pc);
       }
   | day
       {
         pc->days_seen++;
-        DEBUG_PRINT_CURRENT_TIME ("day", pc);
+        DEBUG_PRINT_CURRENT_TIME (_("day"), pc);
       }
   | rel
       {
-        DEBUG_PRINT_RELATIVE_TIME ("relative", pc);
+        DEBUG_PRINT_RELATIVE_TIME (_("relative"), pc);
       }
   | number
       {
-        DEBUG_PRINT_RELATIVE_TIME ("number", pc);
+        DEBUG_PRINT_RELATIVE_TIME (_("number"), pc);
       }
   | hybrid
       {
-        DEBUG_PRINT_RELATIVE_TIME ("hybrid", pc);
+        DEBUG_PRINT_RELATIVE_TIME (_("hybrid"), pc);
       }
   ;
 
@@ -759,8 +762,8 @@ date:
            you want portability, use the ISO 8601 format.  */
         if (4 <= $1.digits)
           {
-            DEBUG ("warning: value %ld has %"PRIuMAX" digits. " \
-                   "Assuming YYYY/MM/DD\n", $1.value, $1.digits);
+            DEBUG (_("warning: value %ld has %"PRIuMAX" digits. " \
+                     "Assuming YYYY/MM/DD\n"), $1.value, $1.digits);
 
             pc->year = $1;
             pc->month = $3.value;
@@ -768,8 +771,8 @@ date:
           }
         else
           {
-            DEBUG("warning: value %ld has less than 4 digits. " \
-                  "Assuming MM/DD/YY[YY]\n", $1.value);
+            DEBUG (_("warning: value %ld has less than 4 digits. " \
+                     "Assuming MM/DD/YY[YY]\n"), $1.value);
 
             pc->month = $1.value;
             pc->day = $3.value;
@@ -1186,7 +1189,7 @@ to_year (textint textyear)
   else if (textyear.digits == 2)
     {
       year += year < 69 ? 2000 : 1900;
-      DEBUG ("warning: adjusting year value %ld to %ld\n",
+      DEBUG (_("warning: adjusting year value %ld to %ld\n"),
              textyear.value, year);
     }
 
@@ -1454,7 +1457,7 @@ yylex (union YYSTYPE *lvalp, parser_control *pc)
           tp = lookup_word (pc, buff);
           if (! tp)
             {
-              DEBUG ("error: unknown word '%s'\n", buff);
+              DEBUG (_("error: unknown word '%s'\n"), buff);
               return '?';
             }
           lvalp->intval = tp->value;
@@ -1626,22 +1629,23 @@ debug_mktime_not_ok (struct tm const *tm0, struct tm const *tm1,
                          && eq_mday && eq_month && eq_year;
 
   DEBUG0 ("error: invalid date/time value:\n");
-  DEBUG ("    user provided time: '%s'\n",
+  DEBUG (_("    user provided time: '%s'\n"),
         debug_strfdatetime (tm0, pc, tmp, sizeof (tmp)));
-  DEBUG ("       normalized time: '%s'\n",
+  DEBUG (_("       normalized time: '%s'\n"),
         debug_strfdatetime (tm1, pc, tmp, sizeof (tmp)));
-  /* NOTE: the format must be aligned with  debug_strfdatetime() */
+  /* NOTEs: the format must be aligned with  debug_strfdatetime().
+            this string is not translated. */
   DEBUG ("                                 %4s %2s %2s %2s %2s %2s\n",
          eq_year?"":"----", eq_month?"":"--", eq_mday?"":"--",
          eq_hour?"":"--", eq_min?"":"--", eq_sec?"":"--");
 
-  DEBUG0 ("     possible reasons:\n");
+  DEBUG0 (_("     possible reasons:\n"));
   if (dst_shift)
-    DEBUG0 ("       non-existing due to daylight-saving time;\n");
+    DEBUG0 (_("       non-existing due to daylight-saving time;\n"));
   if (!eq_mday && !eq_month)
-    DEBUG0 ("       invalid day/month combination;\n");
-  DEBUG0 ("       numeric values overflow;\n");
-  DEBUG ("       %s timezone;\n", time_zone_seen?"incorrect":"missing");
+    DEBUG0 (_("       invalid day/month combination;\n"));
+  DEBUG0 (_("       numeric values overflow;\n"));
+  DEBUG0 (time_zone_seen?_("incorrect timezone"):_("missing timezone"));
 }
 
 
@@ -1837,7 +1841,7 @@ parse_datetime (struct timespec *result, char const *p,
 
   if (yyparse (&pc) != 0)
     {
-      DEBUG ("error: parsing failed, stopped at '%s'\n", pc.input);
+      DEBUG (_("error: parsing failed, stopped at '%s'\n"), pc.input);
       goto fail;
     }
 
@@ -1851,39 +1855,39 @@ parse_datetime (struct timespec *result, char const *p,
       if (pc.timespec_seen)
         {
           tz = 0 ;
-          tz_src = "'@timespec' - always UTC0" ;
+          tz_src = _("'@timespec' - always UTC0");
         }
       else if (pc.local_zones_seen || pc.zones_seen)
         {
           tz = pc.time_zone;
-          tz_src = "parsed date/time string";
+          tz_src = _("parsed date/time string");
         }
       else if ((tz_env = getenv("TZ")))
         {
           if (tz_was_altered)
             {
-              snprintf (dbg_tm, sizeof(dbg_tm), "TZ=\"%s\" in date string",
+              snprintf (dbg_tm, sizeof(dbg_tm), _("TZ=\"%s\" in date string"),
                         tz_env);
               tz_src = dbg_tm;
             }
           else if (STREQ(tz_env,"UTC0"))
             {
               /* Special case: using 'date -u' simply set TZ=UTC0 */
-              tz_src = "TZ=UTC0 environment value or -u";
+              tz_src = _("TZ=UTC0 environment value or -u");
             }
           else
             {
-              snprintf (dbg_tm, sizeof(dbg_tm), "TZ=\"%s\" environment value",
-                        tz_env);
+              snprintf (dbg_tm, sizeof(dbg_tm),
+                        _("TZ=\"%s\" environment value"), tz_env);
               tz_src = dbg_tm;
             }
         }
       else
         {
-          tz_src = "system default";
+          tz_src = _("system default");
         }
 
-      PROGRESS ("input timezone: %+03d:%02d (set from %s)\n",
+      PROGRESS (_("input timezone: %+03d:%02d (set from %s)\n"),
                 (int)(tz/60), abs ((int)tz)%60, tz_src);
 
     }
@@ -1921,14 +1925,14 @@ parse_datetime (struct timespec *result, char const *p,
             {
               const char* mrd = (pc.meridian==MERam)?"am":
                                   (pc.meridian==MERpm)?"pm":"";
-              DEBUG ("error: invalid hour %ld%s\n", pc.hour, mrd);
+              DEBUG (_("error: invalid hour %ld%s\n"), pc.hour, mrd);
 
               goto fail;
             }
           tm.tm_min = pc.minutes;
           tm.tm_sec = pc.seconds.tv_sec;
-          PROGRESS ("using %s time as starting value: '%s'\n",
-                    (pc.times_seen)?"specified":"current",
+          PROGRESS (_("using %s time as starting value: '%s'\n"),
+                    (pc.times_seen)?_("specified"):_("current"),
                     debug_strftime (&tm,dbg_tm,sizeof (dbg_tm)));
         }
       else
@@ -1984,7 +1988,7 @@ parse_datetime (struct timespec *result, char const *p,
               if (setenv ("TZ", tz1buf, 1) != 0)
                 {
                   /* TODO: was warn () + print errno? */
-                  DEBUG ("error: setenv('TZ','%s') failed\n", tz1buf);
+                  DEBUG (_("error: setenv('TZ','%s') failed\n"), tz1buf);
 
                   goto fail;
                 }
@@ -2010,8 +2014,8 @@ parse_datetime (struct timespec *result, char const *p,
           Start = mktime (&tm);
           if (Start == (time_t) -1)
             {
-              DEBUG ("error: day '%s' (day ordinal=%ld number=%d) "      \
-                     "resulted in an invalid date: '%s'\n",
+              DEBUG (_("error: day '%s' (day ordinal=%ld number=%d) " \
+                       "resulted in an invalid date: '%s'\n"),
                      str_days (&pc,dbg_ord,sizeof (dbg_ord)),
                      pc.day_ordinal,pc.day_number,
                      debug_strfdatetime (&tm, &pc, dbg_tm,sizeof (dbg_tm)));
@@ -2019,21 +2023,21 @@ parse_datetime (struct timespec *result, char const *p,
               goto fail;
             }
 
-          PROGRESS ("new start date: '%s' is '%s'\n",
+          PROGRESS (_("new start date: '%s' is '%s'\n"),
                     str_days (&pc,dbg_ord,sizeof (dbg_ord)),
                     debug_strfdatetime (&tm, &pc, dbg_tm,sizeof (dbg_tm)));
 
         }
 
       if (!pc.dates_seen && !pc.days_seen)
-        PROGRESS ("using current date as starting value: '%s'\n",
+        PROGRESS (_("using current date as starting value: '%s'\n"),
                   debug_strfdate (&tm,dbg_tm,sizeof (dbg_tm)));
 
       if (pc.days_seen && pc.dates_seen)
-        DEBUG ("warning: day (%s) ignored when explicit dates are given\n",
+        DEBUG (_("warning: day (%s) ignored when explicit dates are given\n"),
                str_days (&pc,dbg_ord,sizeof (dbg_ord)));
 
-      PROGRESS ("starting date/time: '%s'\n",
+      PROGRESS (_("starting date/time: '%s'\n"),
                 debug_strfdatetime (&tm, &pc, dbg_tm,sizeof (dbg_tm)));
 
 
@@ -2057,7 +2061,7 @@ parse_datetime (struct timespec *result, char const *p,
               | ((day < tm.tm_mday) ^ (pc.rel.day < 0)))
             {
               /* TODO: what is the actual error? int-value wrap-around? */
-              DEBUG ("error: %s:%d\n", __FILE__,__LINE__);
+              DEBUG (_("error: %s:%d\n"), __FILE__,__LINE__);
 
               goto fail;
             }
@@ -2071,17 +2075,17 @@ parse_datetime (struct timespec *result, char const *p,
           Start = mktime (&tm);
           if (Start == (time_t) -1)
             {
-              DEBUG ("error: adding relative date resulted "  \
-                     "in an invalid date: '%s'\n",
+              DEBUG (_("error: adding relative date resulted "  \
+                       "in an invalid date: '%s'\n"),
                      debug_strfdatetime (&tm, &pc, dbg_tm, sizeof (dbg_tm)));
 
               goto fail;
             }
 
-          PROGRESS ("after date adjustment "\
-                    "(%+ld years, %+ld months, %+ld days),\n",
+          PROGRESS (_("after date adjustment "\
+                      "(%+ld years, %+ld months, %+ld days),\n"),
                     pc.rel.year, pc.rel.month, pc.rel.day);
-          PROGRESS ("    new date/time = '%s'\n",
+          PROGRESS (_("    new date/time = '%s'\n"),
                     debug_strfdatetime (&tm, &pc, dbg_tm, sizeof (dbg_tm)));
 
         }
@@ -2100,7 +2104,7 @@ parse_datetime (struct timespec *result, char const *p,
           if (! gmt)
             {
               /* TODO: use 'warn(3)' + print errno ? */
-              DEBUG ("error: gmtime failed for t=%ld\n",t);
+              DEBUG (_("error: gmtime failed for t=%ld\n"),t);
 
               goto fail;
             }
@@ -2109,7 +2113,7 @@ parse_datetime (struct timespec *result, char const *p,
           t1 = Start - delta;
           if ((Start < t1) != (delta < 0))
             {
-              DEBUG ("error: timezone %ld caused time_t overflow\n",
+              DEBUG (_("error: timezone %ld caused time_t overflow\n"),
                      pc.time_zone);
 
               goto fail;  /* time_t overflow */
@@ -2117,7 +2121,7 @@ parse_datetime (struct timespec *result, char const *p,
           Start = t1;
         }
 
-      PROGRESS ("'%s' = %ld epoch-seconds\n",
+      PROGRESS (_("'%s' = %ld epoch-seconds\n"),
                 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof (dbg_tm)),
                 Start);
 
@@ -2157,10 +2161,10 @@ parse_datetime (struct timespec *result, char const *p,
 
           if (pc.rel.hour | pc.rel.minutes | pc.rel.seconds | pc.rel.ns )
             {
-              PROGRESS ("after time adjustment "                              \
-                    "(%+ld hours, %+ld minutes, %+ld seconds, %+ld ns),\n",
+              PROGRESS (_("after time adjustment "     \
+                    "(%+ld hours, %+ld minutes, %+ld seconds, %+ld ns),\n"),
                     pc.rel.hour,pc.rel.minutes,pc.rel.seconds,pc.rel.ns);
-              PROGRESS ("    new time = %ld epoch-seconds\n",t5);
+              PROGRESS (_("    new time = %ld epoch-seconds\n"),t5);
             }
 
         result->tv_sec = t5;
@@ -2190,32 +2194,32 @@ parse_datetime (struct timespec *result, char const *p,
           /* Special case: using 'date -u' simply set TZ=UTC0 */
           if (STREQ(tz_env,"UTC0"))
             {
-              tz_src = "TZ=UTC0 envionment value or -u";
+              tz_src = _("TZ=UTC0 envionment value or -u");
             }
           else
             {
-              snprintf (dbg_tm, sizeof(dbg_tm), "TZ=\"%s\" environment value",
-                        tz_env);
+              snprintf (dbg_tm, sizeof(dbg_tm),
+                        _("TZ=\"%s\" environment value"), tz_env);
               tz_src = dbg_tm;
             }
         }
       else
         {
-          tz_src = "system default";
+          tz_src = _("system default");
         }
 
-      PROGRESS ("output timezone: %+03d:%02d (set from %s)\n",
+      PROGRESS (_("output timezone: %+03d:%02d (set from %s)\n"),
                 (int)(otz/60), abs ((int)otz)%60, tz_src);
 
 
-      PROGRESS ("final: %ld.%09ld (epoch-seconds)\n",
-            result->tv_sec,result->tv_nsec);
+      PROGRESS (_("final: %ld.%09ld (epoch-seconds)\n"),
+                result->tv_sec,result->tv_nsec);
 
       struct tm const *gmt = gmtime (&result->tv_sec);
-      PROGRESS ("final: %s (UTC0)\n",
+      PROGRESS (_("final: %s (UTC0)\n"),
                 debug_strfdatetime (gmt, NULL, dbg_tm, sizeof (dbg_tm)));
       struct tm const *lmt = localtime (&result->tv_sec);
-      PROGRESS ("final: %s (output timezone TZ=%+03d:%02d)\n",
+      PROGRESS (_("final: %s (output timezone TZ=%+03d:%02d)\n"),
                 debug_strfdatetime (lmt, NULL, dbg_tm, sizeof (dbg_tm)),
                 (int)(otz/60), abs ((int)otz)%60);
     }
diff --git a/modules/parse-datetime b/modules/parse-datetime
index 9f5e40a..72b41dd 100644
--- a/modules/parse-datetime
+++ b/modules/parse-datetime
@@ -13,6 +13,7 @@ Depends-on:
 c-ctype
 stdbool
 gettime
+gettext-h
 intprops
 mktime
 setenv
-- 
2.7.0

