details:   https://code.tryton.org/tryton/commit/6952308a8b6b
branch:    default
user:      Nicolas Évrard <[email protected]>
date:      Tue Apr 14 18:29:42 2026 +0200
description:
        Parse equality to a date as two clauses when handling datetime fields

        Closes #14778
diffstat:

 sao/src/common.js                                |  26 ++++++++++++++++++
 sao/tests/sao.js                                 |  34 ++++++++++++++++++++++++
 tryton/tryton/common/domain_parser.py            |  20 ++++++++++++++
 tryton/tryton/tests/test_common_domain_parser.py |  29 ++++++++++++++++++++
 4 files changed, 109 insertions(+), 0 deletions(-)

diffs (170 lines):

diff -r a28800bfd0f1 -r 6952308a8b6b sao/src/common.js
--- a/sao/src/common.js Tue Apr 14 18:20:45 2026 +0200
+++ b/sao/src/common.js Tue Apr 14 18:29:42 2026 +0200
@@ -1951,6 +1951,32 @@
                                     ]);
                             return;
                         }
+                        if ((typeof value == 'string') &&
+                            ['datetime', 'timestamp'].includes(field.type) &&
+                            (operator == '=')) {
+                            let ctx, format_, parsed_date;
+                            if (this.context && 
Object.keys(this.context).length) {
+                                ctx = this.context;
+                            } else {
+                                ctx = {};
+                            }
+                            format_ = Sao.common.date_format(ctx.date_format);
+                            parsed_date = Sao.common.parse_date(format_, 
value);
+                            if (parsed_date &&
+                                (Sao.common.format_date(format_, parsed_date) 
== value)) {
+                                let date = Sao.DateTime.combine(parsed_date, 
Sao.Time());
+                                let next_day = Sao.DateTime(
+                                    date.year(), date.month(), date.date(),
+                                    date.hour(), date.minute(), date.second(),
+                                    date.millisecond())
+                                next_day.add(1, 'day');
+                                result.push(this._clausify([
+                                    [field_name, '>=', date],
+                                    [field_name, '<', next_day]
+                                ]));
+                                return;
+                            }
+                        }
                     }
                     if (['many2one', 'one2many', 'many2many', 'one2one',
                         'many2many', 'one2one'].includes(field.type) && value) 
{
diff -r a28800bfd0f1 -r 6952308a8b6b sao/tests/sao.js
--- a/sao/tests/sao.js  Tue Apr 14 18:20:45 2026 +0200
+++ b/sao/tests/sao.js  Tue Apr 14 18:29:42 2026 +0200
@@ -1933,6 +1933,12 @@
                 'name': 'integer',
                 'type': 'integer'
             },
+            'timestamp': {
+                'name': 'timestamp',
+                'string': 'Timestamp',
+                'type': 'timestamp',
+                'format': '"%H:%M:%S"',
+            },
             'selection': {
                 'string': 'Selection',
                 'name': 'selection',
@@ -2033,6 +2039,7 @@
             c(['integer', '>=', 3]),
             c(['integer', '<=', 5])
             ]]],
+        [[c(['Timestamp', null, null])], [c(['timestamp', '=', null])]],
         [[c(['Reference', null, 'foo'])],
             [c(['reference', 'ilike', '%foo%'])]],
         [[c(['Reference', null, 'Spam'])],
@@ -2073,6 +2080,33 @@
             QUnit.assert.deepEqual(parser.parse_clause(value), result,
                 'parse_clause(' + JSON.stringify(value) + ')');
         });
+
+        let clause = parser.parse_clause([c(
+            ['Timestamp', '=', Sao.common.format_date('%x', Sao.Date(2002, 12, 
4))])]);
+        QUnit.assert.strictEqual(clause[0].length, 2);
+        let [ , operator, value] = clause[0][0];
+        QUnit.assert.strictEqual(operator, '>=');
+        QUnit.assert.ok(value.isSame(Sao.Date(2002, 12, 4)));
+        [ , operator, value] = clause[0][1];
+        QUnit.assert.strictEqual(operator, '<');
+        QUnit.assert.ok(value.isSame(Sao.Date(2002, 12, 5)));
+
+        clause = parser.parse_clause([c(
+            ['Timestamp', '=',
+                Sao.common.format_datetime('%x %X', Sao.DateTime(2002, 12, 4, 
12, 30))])]);
+        [ , operator, value] = clause[0];
+        QUnit.assert.strictEqual(operator, '=');
+        QUnit.assert.ok(value.isSame(Sao.DateTime(2002, 12, 4, 12, 30)));
+
+        clause = parser.parse_clause([c(
+            ['Timestamp', null, [
+                        `${Sao.common.format_date('%x', Sao.Date(2002, 12, 
4))}`,
+                        `${Sao.common.format_date('%x', Sao.Date(2002, 12, 
5))}`,
+                        ]])]);
+        [ , operator, value] = clause[0];
+        QUnit.assert.strictEqual(operator, 'in');
+        QUnit.assert.ok(value[0].isSame(Sao.DateTime(2002, 12, 4)));
+        QUnit.assert.ok(value[1].isSame(Sao.DateTime(2002, 12, 5)));
     });
 
     QUnit.test('DomainParser.format_value', function() {
diff -r a28800bfd0f1 -r 6952308a8b6b tryton/tryton/common/domain_parser.py
--- a/tryton/tryton/common/domain_parser.py     Tue Apr 14 18:20:45 2026 +0200
+++ b/tryton/tryton/common/domain_parser.py     Tue Apr 14 18:29:42 2026 +0200
@@ -885,6 +885,26 @@
                                     (field_name, '<=', rvalue),
                                     ])
                             continue
+                        if (isinstance(value, str)
+                                and field['type'] in {'datetime', 'timestamp'}
+                                and operator == '='):
+                            ctx = self.context if self.context else {}
+                            format_ = date_format(ctx.get('date_format'))
+                            try:
+                                dt = datetime.datetime.strptime(
+                                    value, format_).date()
+                            except (ValueError, TypeError):
+                                dt = None
+                            if dt:
+                                date = untimezoned_date(
+                                    datetime.datetime.combine(
+                                        dt, datetime.time()))
+                                next_date = date + datetime.timedelta(days=1)
+                                yield iter([
+                                        (field_name, '>=', date),
+                                        (field_name, '<', next_date),
+                                        ])
+                                continue
                     if field['type'] in {
                             'many2one', 'one2many', 'many2many', 'one2one',
                             } and value:
diff -r a28800bfd0f1 -r 6952308a8b6b 
tryton/tryton/tests/test_common_domain_parser.py
--- a/tryton/tryton/tests/test_common_domain_parser.py  Tue Apr 14 18:20:45 
2026 +0200
+++ b/tryton/tryton/tests/test_common_domain_parser.py  Tue Apr 14 18:29:42 
2026 +0200
@@ -943,6 +943,12 @@
                     'name': 'integer',
                     'type': 'integer',
                     },
+                'timestamp': {
+                    'name': 'timestamp',
+                    'string': 'Timestamp',
+                    'type': 'timestamp',
+                    'format': '"%H:%M:%S"',
+                    },
                 'selection': {
                     'string': 'Selection',
                     'name': 'selection',
@@ -1051,6 +1057,29 @@
                 (
                     [('Integer', None, '3..5')],
                     [[('integer', '>=', 3), ('integer', '<=', 5)]]),
+                ([('Timestamp', None, None)], [('timestamp', '=', None)]),
+                (
+                    [('Timestamp', '=', dt.date(2002, 12, 4).strftime('%x'))],
+                    [[
+                            ('timestamp', '>=',
+                                untimezoned_date(dt.datetime(2002, 12, 4))),
+                            ('timestamp', '<',
+                                untimezoned_date(dt.datetime(2002, 12, 5)))]]),
+                (
+                    [('Timestamp', '=',
+                        dt.datetime(2002, 12, 4, 12, 30).strftime('%x %X'))
+                        ],
+                    [('timestamp', '=', untimezoned_date(
+                                dt.datetime(2002, 12, 4, 12, 30)))]),
+                (
+                    [('Timestamp', None, [
+                                f"{dt.date(2002, 12, 4).strftime('%x')}",
+                                f"{dt.date(2002, 12, 5).strftime('%x')}",
+                                ])],
+                    [('timestamp', 'in', [
+                                untimezoned_date(dt.datetime(2002, 12, 4)),
+                                untimezoned_date(dt.datetime(2002, 12, 5)),
+                                ])]),
                 (
                     [('Reference', None, 'foo')],
                     [('reference', 'ilike', '%foo%')]),

Reply via email to