On 09/02/2026 14:36, Bruno Haible wrote:
Hi,
11/14/2022 # Common U.S. writing.
+14.11.2022 # Common European writing.
That's a good and major improvement.
Yes it means we support all of:
yy-mm-dd
mm/dd/yy
dd.mm.yy
which also means different orders are supported
with simple substitutions of delimiters.
@@ -1469,6 +1491,12 @@ yylex (union YYSTYPE *lvalp, parser_control *pc)
for (digits = 2; digits <= LOG10_BILLION; digits++)
{
ns *= 10;
+ /* Don't parse DD.MM.YYYY dates as a decimal */
+ if (*p == '.') {
+ p = old_p;
+ pc->not_decimal = true;
+ goto normal_value;
+ }
if (c_isdigit (*p))
ns += *p++ - '0';
}
Can we have this 'if' statement reformatted in GNU coding style?
ack (done as part of the second patch attached)
Also, how about adding two test cases in the unit test
tests/test-parse-datetime.c, around line 238 ?
I guess that some test case was already added to coreutils, but since
parse-datetime is a Gnulib module, its essential functionalities should
be covered by the Gnulib unit test.
Done in the first patch attached.
While doing that I noticed inconsistencies, where
`date -d '1.2. 3:4:5.6'` was not supported, while
`date -d '2/1 3:4:5.6'` was supported
`date -d '1.2. 3'` treats 3 as year, while
`date -d '2/1 3'` treats 3 as hour
Therefore I reworked the parsing in the second patch attached,
which keeps support for the short form,
while using it in combination with other date components.
cheers,
Padraig
From b2450db30ec949376adefc2982611dd2fd1443c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email protected]>
Date: Mon, 9 Feb 2026 15:36:46 +0000
Subject: [PATCH 1/2] parse-datetime: add tests for dd.mm.yy and mm/dd/yy
formats
* tests/test-parse-datetime.c: Add test cases.
---
tests/test-parse-datetime.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/tests/test-parse-datetime.c b/tests/test-parse-datetime.c
index 06e98323b7..b560639e2d 100644
--- a/tests/test-parse-datetime.c
+++ b/tests/test-parse-datetime.c
@@ -235,6 +235,41 @@ main (_GL_UNUSED int argc, char **argv)
ASSERT (expected.tv_sec == result.tv_sec
&& expected.tv_nsec == result.tv_nsec);
+ /* DD.MM.YYYY */
+ p = "01.05.2011 11:55:18";
+ expected.tv_sec = ref_time - gmtoff;
+ 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);
+
+ /* DD.MM. */
+ now.tv_sec = SOME_TIMEPOINT + 4711;
+ now.tv_nsec = 1267;
+ p = "01.05";
+ ASSERT (!parse_datetime (&result, p, &now));
+ p = "01.05.";
+ ASSERT (parse_datetime (&result, p, &now));
+ LOG (p, now, result);
+ ASSERT (result.tv_nsec == 0);
+
+ /* MM/DD/YYYY */
+ p = "05/01/2011 11:55:18";
+ expected.tv_sec = ref_time - gmtoff;
+ 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);
+
+ /* MM/DD */
+ now.tv_sec = SOME_TIMEPOINT + 4711;
+ now.tv_nsec = 1267;
+ p = "05/01";
+ ASSERT (parse_datetime (&result, p, &now));
+ LOG (p, now, result);
+ ASSERT (result.tv_nsec == 0);
now.tv_sec = SOME_TIMEPOINT + 4711;
now.tv_nsec = 1267;
--
2.52.0
From 65a2003d42f6ad601ddf0d1c97ffa98cc5c4c6e4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email protected]>
Date: Mon, 9 Feb 2026 17:06:11 +0000
Subject: [PATCH 2/2] parse-datetime: support dd.mm. in combination
* lib/parse-datetime.y: The previous commit added DD.MM. and DD.MM.YYYY
grammar rules, plus lexer logic (NOT_DECIMAL flag) to prevent 17.6 from
being lexed as a single decimal token. The resulting shift/reduce
conflict between the two rules caused "1.2. 6:7:8.9" to consume 6 as
a year. Also the NOT_DECIMAL flag was never cleared in the DD.MM. case,
later preventing 8.9 from being lexed as a decimal.
Since the lexer already distinguishes the contiguous 1.2.3 and spaced,
we introduce tUNUMBER_DOTTED so each rule expects different tokens,
thus removing the shift/reduce conflict. Also we clear NOT_DECIMAL
in the normal path.
* tests/test-parse-datetime.c: Add test cases.
---
lib/parse-datetime.y | 26 ++++++++++++++++----------
tests/test-parse-datetime.c | 22 ++++++++++++++++++++++
2 files changed, 38 insertions(+), 10 deletions(-)
diff --git a/lib/parse-datetime.y b/lib/parse-datetime.y
index 33e0215541..8d396b1273 100644
--- a/lib/parse-datetime.y
+++ b/lib/parse-datetime.y
@@ -575,8 +575,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 32 shift/reduce conflicts. */
-%expect 32
+/* This grammar has 31 shift/reduce conflicts. */
+%expect 31
%union
{
@@ -595,7 +595,7 @@ debug_print_relative_time (char const *item, parser_control const *pc)
%token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
%token <intval> tMONTH tORDINAL tZONE
-%token <textintval> tSNUMBER tUNUMBER
+%token <textintval> tSNUMBER tUNUMBER tUNUMBER_DOTTED
%token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
%type <intval> o_colon_minutes
@@ -855,7 +855,7 @@ date:
pc->day = $1.value;
pc->month = $3.value;
}
- | tUNUMBER '.' tUNUMBER '.' tUNUMBER
+ | tUNUMBER '.' tUNUMBER_DOTTED '.' tUNUMBER
{
/* E.g., 17.6.1992 */
pc->day = $1.value;
@@ -1479,7 +1479,11 @@ yylex (union YYSTYPE *lvalp, parser_control *pc)
if (pc->not_decimal)
{
pc->not_decimal = false;
- goto normal_value;
+ lvalp->textintval.negative = sign < 0;
+ lvalp->textintval.value = value;
+ lvalp->textintval.digits = p - pc->input;
+ pc->input = p;
+ return tUNUMBER_DOTTED;
}
time_t s = value;
int digits;
@@ -1492,11 +1496,12 @@ yylex (union YYSTYPE *lvalp, parser_control *pc)
{
ns *= 10;
/* Don't parse DD.MM.YYYY dates as a decimal */
- if (*p == '.') {
- p = old_p;
- pc->not_decimal = true;
- goto normal_value;
- }
+ if (*p == '.')
+ {
+ p = old_p;
+ pc->not_decimal = true;
+ goto normal_value;
+ }
if (c_isdigit (*p))
ns += *p++ - '0';
}
@@ -1529,6 +1534,7 @@ yylex (union YYSTYPE *lvalp, parser_control *pc)
}
else
{
+ pc->not_decimal = false;
normal_value:
lvalp->textintval.negative = sign < 0;
lvalp->textintval.value = value;
diff --git a/tests/test-parse-datetime.c b/tests/test-parse-datetime.c
index b560639e2d..3176327d2e 100644
--- a/tests/test-parse-datetime.c
+++ b/tests/test-parse-datetime.c
@@ -253,6 +253,10 @@ main (_GL_UNUSED int argc, char **argv)
ASSERT (parse_datetime (&result, p, &now));
LOG (p, now, result);
ASSERT (result.tv_nsec == 0);
+ p = "01.05. 6:7:8.9";
+ ASSERT (parse_datetime (&result, p, NULL));
+ LOG (p, now, result);
+ ASSERT (result.tv_nsec == 900000000);
/* MM/DD/YYYY */
p = "05/01/2011 11:55:18";
@@ -271,6 +275,24 @@ main (_GL_UNUSED int argc, char **argv)
LOG (p, now, result);
ASSERT (result.tv_nsec == 0);
+ /* DD.MM.YY MM/DD.YY equivalence */
+ p = "2.1.3 day";
+ ASSERT (parse_datetime (&result, p, NULL));
+ LOG (p, now, result);
+ p = "1/2/3 day";
+ ASSERT (parse_datetime (&result2, p, NULL));
+ LOG (p, now, result);
+ ASSERT (result.tv_sec == result2.tv_sec);
+
+ /* DD.MM MM/DD equivalence */
+ p = "2.1. 3";
+ ASSERT (parse_datetime (&result, p, NULL));
+ LOG (p, now, result);
+ p = "1/2 3";
+ ASSERT (parse_datetime (&result2, p, NULL));
+ LOG (p, now, result);
+ ASSERT (result.tv_sec == result2.tv_sec);
+
now.tv_sec = SOME_TIMEPOINT + 4711;
now.tv_nsec = 1267;
p = "now";
--
2.52.0