Hi Padraig,
Thanks for the feedback.
I was able to get back to this and have a look at getting this
inconsistency resolved, and I was able to find a change that seems to
resolve this inconsistency.
It is still correctly parsing the timezone with AM/PM:
$ ./src/date --debug -d '2024-01-01 8:00:00PM -0500'
date: parsed date part: (Y-M-D) 2024-01-01
date: parsed time part: 08:00:00pm UTC-05
date: input timezone: parsed date/time string (-05)
date: using specified time as starting value: '20:00:00'
date: starting date/time: '(Y-M-D) 2024-01-01 20:00:00 TZ=-05'
date: '(Y-M-D) 2024-01-01 20:00:00 TZ=-05' = 1704157200 epoch-seconds
date: timezone: system default
date: final: 1704157200.000000000 (epoch-seconds)
date: final: (Y-M-D) 2024-01-02 01:00:00 (UTC)
date: final: (Y-M-D) 2024-01-01 17:00:00 (UTC-08)
date: output format: ‘%a %d %b %Y %T %Z’
Mon 01 Jan 2024 17:00:00 PST
And will now correctly prioritize time offset with a ime unit is specified,
both with AM/PM and without:
$ ./src/date --debug -d '2024-01-01 8:00:00 -5 days'
date: parsed date part: (Y-M-D) 2024-01-01
date: parsed relative part: -5 day(s)
date: parsed time part: 08:00:00
date: input timezone: system default
date: using specified time as starting value: '08:00:00'
date: starting date/time: '(Y-M-D) 2024-01-01 08:00:00'
date: warning: when adding relative days, it is recommended to specify noon
date: after date adjustment (+0 years, +0 months, -5 days),
date: new date/time = '(Y-M-D) 2023-12-27 08:00:00'
date: '(Y-M-D) 2023-12-27 08:00:00' = 1703692800 epoch-seconds
date: timezone: system default
date: final: 1703692800.000000000 (epoch-seconds)
date: final: (Y-M-D) 2023-12-27 16:00:00 (UTC)
date: final: (Y-M-D) 2023-12-27 08:00:00 (UTC-08)
date: output format: ‘%a %d %b %Y %T %Z’
Wed 27 Dec 2023 08:00:00 PST
$ ./src/date --debug -d '2024-01-01 8:00:00AM -5 days'
date: parsed date part: (Y-M-D) 2024-01-01
date: parsed relative part: -5 day(s)
date: parsed time part: 08:00:00
date: input timezone: system default
date: using specified time as starting value: '08:00:00'
date: starting date/time: '(Y-M-D) 2024-01-01 08:00:00'
date: warning: when adding relative days, it is recommended to specify noon
date: after date adjustment (+0 years, +0 months, -5 days),
date: new date/time = '(Y-M-D) 2023-12-27 08:00:00'
date: '(Y-M-D) 2023-12-27 08:00:00' = 1703692800 epoch-seconds
date: timezone: system default
date: final: 1703692800.000000000 (epoch-seconds)
date: final: (Y-M-D) 2023-12-27 16:00:00 (UTC)
date: final: (Y-M-D) 2023-12-27 08:00:00 (UTC-08)
date: output format: ‘%a %d %b %Y %T %Z’
Wed 27 Dec 2023 08:00:00 PST
And the coreutils complete testsuite is passing:
============================================================================
Testsuite summary for GNU coreutils 9.9.31-1d58e
============================================================================
# TOTAL: 575
# PASS: 518
# SKIP: 57
# XFAIL: 0
# FAIL: 0
# XPASS: 0
# ERROR: 0
============================================================================
Below is the full patch:
diff --git a/lib/parse-datetime.y b/lib/parse-datetime.y
index 6c52cd2c4c..71f8b9ae0e 100644
--- a/lib/parse-datetime.y
+++ b/lib/parse-datetime.y
@@ -573,8 +573,8 @@ debug_print_relative_time (char const *item,
parser_control const *pc)
%parse-param { parser_control *pc }
%lex-param { parser_control *pc }
-/* This grammar has 31 shift/reduce conflicts. */
-%expect 31
+/* This grammar has 40 shift/reduce conflicts. */
+%expect 40
%union
{
@@ -681,17 +681,17 @@ iso_8601_datetime:
;
time:
- tUNUMBER tMERIDIAN
+ tUNUMBER tMERIDIAN o_zone_offset
{
set_hhmmss (pc, $1.value, 0, 0, 0);
pc->meridian = $2;
}
- | tUNUMBER ':' tUNUMBER tMERIDIAN
+ | tUNUMBER ':' tUNUMBER tMERIDIAN o_zone_offset
{
set_hhmmss (pc, $1.value, $3.value, 0, 0);
pc->meridian = $4;
}
- | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tMERIDIAN
+ | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tMERIDIAN o_zone_offset
{
set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
pc->meridian = $6;
@@ -720,6 +720,11 @@ iso_8601_time:
o_zone_offset:
/* empty */
| zone_offset
+ | relunit_snumber
+ {
+ if (! apply_relative_time (pc, $1, 1)) YYABORT;
+ debug_print_relative_time (_("relative"), pc);
+ }
;
zone_offset:
diff --git a/tests/test-parse-datetime.c b/tests/test-parse-datetime.c
index 23c8598a6f..8bfb7210d6 100644
--- a/tests/test-parse-datetime.c
+++ b/tests/test-parse-datetime.c
@@ -334,6 +334,31 @@ main (_GL_UNUSED int argc, char **argv)
ASSERT (result.tv_sec == result2.tv_sec
&& result.tv_nsec == result2.tv_nsec);
+ /* Check that timeone works with AM/PM */
+ p = "2024-01-01 8PM -08:00";
+ expected.tv_sec = 1704168000;
+ expected.tv_nsec = 0;
+ ASSERT (parse_datetime (&result, p, NULL));
+ LOG (p, expected, result);
+ ASSERT (expected.tv_sec == result.tv_sec
+ && expected.tv_nsec == result.tv_nsec);
+
+ /* Check that date offset is preferred to timezone */
+ p = "2024-01-01 8PM -5 days";
+ expected.tv_sec = 1703725200;
+ expected.tv_nsec = 0;
+ ASSERT (parse_datetime (&result, p, NULL));
+ LOG (p, expected, result);
+ ASSERT (expected.tv_sec == result.tv_sec
+ && expected.tv_nsec == result.tv_nsec);
+
+ p = "2024-01-01 20:00 -5 days";
+ expected.tv_sec = 1703725200;
+ expected.tv_nsec = 0;
+ ASSERT (parse_datetime (&result, p, NULL));
+ LOG (p, expected, result);
+ ASSERT (expected.tv_sec == result.tv_sec
+ && expected.tv_nsec == result.tv_nsec);
/* TZ out of range should cause parse_datetime failure */
now.tv_sec = SOME_TIMEPOINT + 4711;
Regards.
Jeff
On Tue, 29 Jul 2025 at 04:22, Pádraig Brady <[email protected]> wrote:
> On 29/07/2025 06:02, Jeffery Palm wrote:
> > I took a look at this bug, and believe I have a patch that will resolve
> it.
> >
> > $ ../src/date --debug -d '2024-01-01 8:00:00PM -0500'
> > date: parsed date part: (Y-M-D) 2024-01-01
> > date: parsed time part: 08:00:00pm UTC-05
> > date: input timezone: parsed date/time string (-05)
> > date: using specified time as starting value: '20:00:00'
> > date: starting date/time: '(Y-M-D) 2024-01-01 20:00:00 TZ=-05'
> > date: '(Y-M-D) 2024-01-01 20:00:00 TZ=-05' = 1704157200 epoch-seconds
> > date: timezone: system default
> > date: final: 1704157200.000000000 (epoch-seconds)
> > date: final: (Y-M-D) 2024-01-02 01:00:00 (UTC)
> > date: final: (Y-M-D) 2024-01-01 17:00:00 (UTC-08)
> > date: output format: ‘%a %d %b %Y %T %Z’
> > Mon 01 Jan 2024 17:00:00 PST
> >
> >
> > And I was able to run the coreutils testsuite with no tests failing:
> >
> >
> ============================================================================
> > Testsuite summary for GNU coreutils 9.7.174-083f8
> >
> ============================================================================
> > # TOTAL: 533
> > # PASS: 476
> > # SKIP: 57
> > # XFAIL: 0
> > # FAIL: 0
> > # XPASS: 0
> > # ERROR: 0
> >
> ============================================================================
> >
> > Are there any other tests/changes I should consider for this?
> >
> >
> > Below is the patch for the changes I made for this, including a new
> > testcase for AM/PM with timezone.
> >
> >
> > --- a/lib/parse-datetime.y
> > +++ b/lib/parse-datetime.y
> > @@ -592,7 +592,7 @@ debug_print_relative_time (char const *item,
> > parser_control const *pc)
> > %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
> > %token <intval> tDAY_UNIT tDAY_SHIFT
> >
> > -%token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
> > +%token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN tMERIDIAN_WITH_ZONE
> > %token <intval> tMONTH tORDINAL tZONE
> >
> > %token <textintval> tSNUMBER tUNUMBER
> > @@ -698,6 +698,27 @@ time:
> > set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
> > pc->meridian = $6;
> > }
> > + | tUNUMBER tMERIDIAN_WITH_ZONE tSNUMBER o_colon_minutes
> > + {
> > + set_hhmmss (pc, $1.value, 0, 0, 0);
> > + pc->meridian = $2;
> > + pc->zones_seen++;
> > + if (! time_zone_hhmm (pc, $3, $4)) YYABORT;
> > + }
> > + | tUNUMBER ':' tUNUMBER tMERIDIAN_WITH_ZONE tSNUMBER o_colon_minutes
> > + {
> > + set_hhmmss (pc, $1.value, $3.value, 0, 0);
> > + pc->meridian = $4;
> > + pc->zones_seen++;
> > + if (! time_zone_hhmm (pc, $5, $6)) YYABORT;
> > + }
> > + | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tMERIDIAN_WITH_ZONE
> > tSNUMBER o_colon_minutes
> > + {
> > + set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
> > + pc->meridian = $6;
> > + pc->zones_seen++;
> > + if (! time_zone_hhmm (pc, $7, $8)) YYABORT;
> > + }
> > | iso_8601_time
> > ;
> >
> > @@ -1527,14 +1548,19 @@ yylex (union YYSTYPE *lvalp, parser_control *pc)
> >
> > *p = '\0';
> > tp = lookup_word (pc, buff);
> > - if (! tp)
> > + if (tp)
> > {
> > - if (debugging (pc))
> > - dbg_printf (_("error: unknown word '%s'\n"), buff);
> > - return '?';
> > + lvalp->intval = tp->value;
> > + if (tp->type == tMERIDIAN)
> > + {
> > + char const *p = pc->input;
>
> Better to use a non shadowing name here ^
>
> > + while (*p && c_isspace (*p))
> > + p++;
> > + if (*p == '-' || *p == '+')
> > + return tMERIDIAN_WITH_ZONE;
> > + }
> > + return tp->type;
> > }
> > - lvalp->intval = tp->value;
> > - return tp->type;
> > }
> >
> > if (c != '(')
> > diff --git a/tests/test-parse-datetime.c b/tests/test-parse-datetime.c
> > index 546b383c55..9766ed7a13 100644
> > --- a/tests/test-parse-datetime.c
> > +++ b/tests/test-parse-datetime.c
> > @@ -335,6 +335,15 @@ main (_GL_UNUSED int argc, char **argv)
> > ASSERT (result.tv_sec == result2.tv_sec
> > && result.tv_nsec == result2.tv_nsec);
> >
> > + /* Check that timeone works with AM/PM */
> > + p = "2024-01-01 8PM -08:00";
> > + expected.tv_sec = 1704168000;
> > + expected.tv_nsec = 0;
> > + ASSERT (parse_datetime (&result, p, NULL));
> > + LOG (p, expected, result);
> > + ASSERT (expected.tv_sec == result.tv_sec
> > + && expected.tv_nsec == result.tv_nsec);
> > +
> >
> > /* TZ out of range should cause parse_datetime failure */
> > now.tv_sec = SOME_TIMEPOINT + 4711;
> Thanks for looking at this.
> This changes relative handling unfortunately:
>
> $ src/date --debug -d '2024-01-01 8:00:00PM -5 days'
> date: parsed date part: (Y-M-D) 2024-01-01
> date: parsed time part: 08:00:00pm UTC-05
> date: parsed relative part: +1 day(s)
> ...
> Wed 03 Jan 2024 01:00:00 GMT
>
> $ date --debug -d '2024-01-01 8:00:00PM -5 days'
> date: parsed date part: (Y-M-D) 2024-01-01
> date: parsed time part: 08:00:00pm
> date: parsed relative part: -5 day(s)
> ...
> Wed 27 Dec 2023 20:00:00 GMT
>
> Now there is an existing ambiguity here,
> where the AM/PM induces the relative interpretation:
>
> $ date --debug -d '2024-01-01 8:00:00PM -5 days'
> date: parsed date part: (Y-M-D) 2024-01-01
> date: parsed time part: 08:00:00pm
> date: parsed relative part: -5 day(s)
>
> $ date --debug -d '2024-01-01 8:00:00 -5 days'
> date: parsed date part: (Y-M-D) 2024-01-01
> date: parsed time part: 08:00:00 UTC-05
> date: parsed relative part: +1 day(s)
>
> BTW https://bugs.gnu.org/79078 was a recent bug report
> along the same lines of the relative part being unexpectedly
> considered as a timezone offset
>
> Now I agree we're already inconsistent in this regard, but I'm sure
> folks are relying on the AM/PM inducing a relative interpretation.
>
> If we were trying to make all this more consistent, IMHO
> we should change things so that we always interpret +|-<int>
> <days|minutes|...>
> as a relative adjustment, whereas your change does the opposite for the
> AM/PM case.
>
> cheers,
> Padraig
>