From f14eff1b3cde2bf8198b836ab4da05b4098358cb Mon Sep 17 00:00:00 2001 From: Assaf Gordon Date: Wed, 4 Jan 2017 23:11:33 -0500 Subject: [PATCH] parse-datetime: add debug warning about date arithmetic Date arithmetic are done directly on the fields of 'struct tm', which can result in invalid dates. Normalization with 'mktime(3)' will then produce a different date - which might cause unexpected results. Examples: '2016-10-31 - 1 month' => 2016-09-31 normalized to 2016-10-01. '2016-02-29 + 1 year' => 2017-02-29 normalized to 2017-03-01. Note that date normalization is not inherently wrong and not rejected, as it has legitimate uses: '2016-12-29 + 5 days' => 2016-12-34 noramlized to 2017-01-03. If the user asked to adjust months but 'mday' changed, or user asked to adjust years but 'month' changed - warn about it. $ ./src/date --debug -d '2016-10-31 - 1 month' ... date: warning: when adding relative months/years, \ it is recommended to specify the 15th of the month ... date: warning: month/year adjustment resulted in shifted dates: date: adjusted Y M D: 2016 09 31 date: normalized Y M D: 2010 10 01 ... * lib/parse-datetime.y (parse_datetime2): Detect such cases and print a warning message. Improve recommendation of when to use 15 of the month or noon for date arithmetic. --- ChangeLog | 26 ++++++++++++++++++++++++++ lib/parse-datetime.y | 28 ++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7b2cb1de6e..5753becf6f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,31 @@ 2017-01-04 Assaf Gordon + parse-datetime: add debug warning about date arithmetic + Date arithmetic are done directly on the fields of 'struct tm', + which can result in invalid dates. Normalization with 'mktime(3)' + will then produce a different date - which might cause unexpected + results. + Examples: + '2016-10-31 - 1 month' => 2016-09-31 normalized to 2016-10-01. + '2016-02-29 + 1 year' => 2017-02-29 normalized to 2017-03-01. + Note that date normalization is not inherently wrong and not rejected, + as it has legitimate uses: + '2016-12-29 + 5 days' => 2016-12-34 noramlized to 2017-01-03. + If the user asked to adjust months but 'mday' changed, + or user asked to adjust years but 'month' changed - warn about it. + $ ./src/date --debug -d '2016-10-31 - 1 month' + ... + date: warning: when adding relative months/years, \ + it is recommended to specify the 15th of the month + ... + date: warning: month/year adjustment resulted in shifted dates: + date: adjusted Y M D: 2016 09 31 + date: normalized Y M D: 2010 10 01 + ... + * lib/parse-datetime.y (parse_datetime2): Detect such cases and print + a warning message. Improve recommendation of when to use 15 of the + month or noon for date arithmetic. + parse-datetime: fix debug message of relative part after timezone Relative part (e.g '+8 days') after a timezone string was not reported (was only reported after a timezone number). Due to the diff --git a/lib/parse-datetime.y b/lib/parse-datetime.y index 7196113232..2e0a6096e4 100644 --- a/lib/parse-datetime.y +++ b/lib/parse-datetime.y @@ -2090,12 +2090,12 @@ parse_datetime2 (struct timespec *result, char const *p, { if (pc.parse_datetime_debug) { - if ((pc.rel.year != 0 || pc.rel.month !=0) && tm.tm_mday==1) + if ((pc.rel.year != 0 || pc.rel.month !=0) && tm.tm_mday!=15) dbg_printf (_("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) + if (pc.rel.day != 0 && tm.tm_hour!=12) dbg_printf (_("warning: when adding relative days, " \ "it is recommended to specify 12:00pm\n")); } @@ -2140,6 +2140,30 @@ parse_datetime2 (struct timespec *result, char const *p, dbg_printf (_(" new date/time = '%s'\n"), debug_strfdatetime (&tm, &pc, dbg_tm, sizeof (dbg_tm))); + + /* warn if the user did not ask to adjust days but mday changed, + or + user did not ask to adjust months/days but the month changed. + + Example for first case: + 2016-05-31 + 1 month => 2016-06-31 => 2016-07-01. + User asked to adjust month, but the day changed from 31 to 01. + + Example for second case: + 2016-02-29 + 1 year => 2017-02-29 => 2017-03-01. + User asked to adjust year, but the month changed from 02 to 03. + */ + if (((pc.rel.day==0) && (tm.tm_mday != day)) + || ((pc.rel.day==0) && (pc.rel.month==0) + && (tm.tm_mon != month))) + { + dbg_printf (_("warning: month/year adjustment resulted in "\ + "shifted dates:\n")); + dbg_printf (_(" adjusted Y M D: %04d %02d %02d\n"), + year+1900, month+1, day); + dbg_printf (_(" normalized Y M D: %04d %02d %02d\n"), + tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday); + } } } -- 2.39.5