Parse equality to a date as two clauses when handling datetime fields
Closes #14778 (grafted from 6952308a8b6b65ff583a2b04d9cd558b44c4e4a9) --HG-- branch : 7.0
This commit is contained in:
@@ -1930,6 +1930,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 (value instanceof Array) {
|
||||
value = value.map(
|
||||
|
||||
@@ -1908,6 +1908,12 @@
|
||||
'name': 'integer',
|
||||
'type': 'integer'
|
||||
},
|
||||
'timestamp': {
|
||||
'name': 'timestamp',
|
||||
'string': 'Timestamp',
|
||||
'type': 'timestamp',
|
||||
'format': '"%H:%M:%S"',
|
||||
},
|
||||
'selection': {
|
||||
'string': 'Selection',
|
||||
'name': 'selection',
|
||||
@@ -1996,6 +2002,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'])],
|
||||
@@ -2027,6 +2034,33 @@
|
||||
QUnit.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() {
|
||||
|
||||
@@ -880,6 +880,26 @@ class DomainParser(object):
|
||||
(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 isinstance(value, list):
|
||||
value = [convert_value(field, v, self.context)
|
||||
for v in value]
|
||||
|
||||
@@ -930,6 +930,12 @@ class DomainParserTestCase(TestCase):
|
||||
'name': 'integer',
|
||||
'type': 'integer',
|
||||
},
|
||||
'timestamp': {
|
||||
'name': 'timestamp',
|
||||
'string': 'Timestamp',
|
||||
'type': 'timestamp',
|
||||
'format': '"%H:%M:%S"',
|
||||
},
|
||||
'selection': {
|
||||
'string': 'Selection',
|
||||
'name': 'selection',
|
||||
@@ -1119,6 +1125,33 @@ class DomainParserTestCase(TestCase):
|
||||
self.assertEqual(
|
||||
rlist(dom.parse_clause(iter([['AND']]))),
|
||||
[('rec_name', 'ilike', "%AND%")])
|
||||
for search_text, domain in [
|
||||
([('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)),
|
||||
])]),
|
||||
]:
|
||||
with self.subTest(f'parse_clause({search_text})'):
|
||||
self.assertEqual(rlist(dom.parse_clause(search_text)), domain)
|
||||
|
||||
def test_completion_char(self):
|
||||
"Test completion char"
|
||||
|
||||
Reference in New Issue
Block a user