diff --git a/addons/web/static/src/js/core/py_utils.js b/addons/web/static/src/js/core/py_utils.js
index 590d5e62fc6a4759a033fff072bb67fcdc508aed..1cf1150ab764d535ef2d42cf7c9fedaed21e6374 100644
--- a/addons/web/static/src/js/core/py_utils.js
+++ b/addons/web/static/src/js/core/py_utils.js
@@ -254,14 +254,29 @@ function tz_offset() {
 
 
 function pycontext() {
+    const d = new Date();
+    const today = `${
+        String(d.getFullYear()).padStart(4, "0")}-${
+        String(d.getMonth() + 1).padStart(2, "0")}-${
+        String(d.getDate()).padStart(2, "0")}`;
+    const now = `${
+        String(d.getUTCFullYear()).padStart(4, "0")}-${
+        String(d.getUTCMonth() + 1).padStart(2, "0")}-${
+        String(d.getUTCDate()).padStart(2, "0")} ${
+        String(d.getUTCHours()).padStart(2, "0")}:${
+        String(d.getUTCMinutes()).padStart(2, "0")}:${
+        String(d.getUTCSeconds()).padStart(2, "0")}`;
+
+    const { datetime, relativedelta, time } = py.extras;
     return {
-        datetime: py.extras.datetime,
-        context_today: context_today,
-        time: py.extras.time,
-        relativedelta: py.extras.relativedelta,
-        current_date: py.PY_call(
-            py.extras.time.strftime, [py.str.fromJSON('%Y-%m-%d')]),
-        tz_offset: tz_offset,
+        current_date: today,
+        datetime,
+        time,
+        now,
+        today,
+        relativedelta,
+        context_today,
+        tz_offset,
     };
 }
 
diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js
index adefa997e9985fd1afd68a0d6d688dbaa4231988..8878de55a3ed0ef18d525e28bc6a2b8d1b033c9e 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -88,6 +88,7 @@ var concurrency = require('web.concurrency');
 var Context = require('web.Context');
 var core = require('web.core');
 var Domain = require('web.Domain');
+const pyUtils = require('web.py_utils');
 var session = require('web.session');
 var utils = require('web.utils');
 var viewUtils = require('web.viewUtils');
@@ -3696,19 +3697,27 @@ var BasicModel = AbstractModel.extend({
         }
         // Uses "current_company_id" because "company_id" would conflict with all the company_id fields
         // in general, the actual "company_id" field of the form should be used for m2o domains, not this fallback
+        let current_company_id;
         if (session.user_context.allowed_company_ids) {
-            var current_company = session.user_context.allowed_company_ids[0];
+            current_company_id = session.user_context.allowed_company_ids[0];
         } else {
-            var current_company = session.user_companies ? session.user_companies.current_company[0] : false;
-        }
-        return _.extend({
-            active_id: evalContext.id || false,
-            active_ids: evalContext.id ? [evalContext.id] : [],
-            active_model: element.model,
-            current_date: moment().format('YYYY-MM-DD'),
-            id: evalContext.id || false,
-            current_company_id: current_company,
-        }, session.user_context, element.context, evalContext);
+            current_company_id = session.user_companies ?
+                session.user_companies.current_company[0] :
+                false;
+        }
+        return Object.assign(
+            {
+                active_id: evalContext.id || false,
+                active_ids: evalContext.id ? [evalContext.id] : [],
+                active_model: element.model,
+                current_company_id,
+                id: evalContext.id || false,
+            },
+            pyUtils.context(),
+            session.user_context,
+            element.context,
+            evalContext,
+        );
     },
     /**
      * Returns the list of field names of the given element according to its
diff --git a/addons/web/static/tests/views/basic_model_tests.js b/addons/web/static/tests/views/basic_model_tests.js
index f57478e12522c8d548ec9b78974faf386d95d766..b533c6587e94ef638541e035c0e3d035e7d1ff08 100644
--- a/addons/web/static/tests/views/basic_model_tests.js
+++ b/addons/web/static/tests/views/basic_model_tests.js
@@ -2037,8 +2037,9 @@ odoo.define('web.basic_model_tests', function (require) {
         });
 
         QUnit.test('has a proper evaluation context', async function (assert) {
-            assert.expect(1);
+            assert.expect(6);
 
+            const unpatchDate = testUtils.mock.patchDate(1997, 0, 9, 12, 0, 0);
             this.params.fieldNames = Object.keys(this.data.partner.fields);
             this.params.res_id = 1;
 
@@ -2048,8 +2049,24 @@ odoo.define('web.basic_model_tests', function (require) {
             });
 
             var resultID = await model.load(this.params);
-            var record = model.get(resultID);
-            assert.deepEqual(record.evalContext, {
+            const { evalContext } = model.get(resultID);
+            assert.strictEqual(typeof evalContext.datetime, "object");
+            assert.strictEqual(typeof evalContext.relativedelta, "object");
+            assert.strictEqual(typeof evalContext.time, "object");
+            assert.strictEqual(typeof evalContext.context_today, "function");
+            assert.strictEqual(typeof evalContext.tz_offset, "function");
+            const blackListedKeys = [
+                "time",
+                "datetime",
+                "relativedelta",
+                "context_today",
+                "tz_offset",
+            ];
+            // Remove uncomparable values from the evaluation context
+            for (const key of blackListedKeys) {
+                delete evalContext[key];
+            }
+            assert.deepEqual(evalContext, {
                 active: true,
                 active_id: 1,
                 active_ids: [1],
@@ -2058,6 +2075,8 @@ odoo.define('web.basic_model_tests', function (require) {
                 category: [12],
                 current_company_id: false,
                 current_date: moment().format('YYYY-MM-DD'),
+                today: moment().format('YYYY-MM-DD'),
+                now: moment().utc().format('YYYY-MM-DD HH:mm:ss'),
                 date: "2017-01-25",
                 display_name: "first partner",
                 foo: "blip",
@@ -2070,6 +2089,7 @@ odoo.define('web.basic_model_tests', function (require) {
                 x_active: true,
             }, "should use the proper eval context");
             model.destroy();
+            unpatchDate();
         });
 
         QUnit.test('x2manys in contexts and domains are correctly evaluated', async function (assert) {
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index e467d39d97bc8905e27e8133b01eb1f4d9f011d5..7ecc9093b9a88ab5970e1acb3f8ecddac82b83e1 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -10320,6 +10320,95 @@ QUnit.module('Views', {
 
         list.destroy();
     });
+
+    QUnit.test("Date in evaluation context works with date field", async function (assert) {
+        assert.expect(11);
+
+        const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
+        const unpatchDate = testUtils.mock.patchDate(1997, 0, 9, 12, 0, 0);
+        testUtils.mock.patch(BasicModel, {
+            _getEvalContext() {
+                const evalContext = this._super(...arguments);
+                assert.ok(dateRegex.test(evalContext.today));
+                assert.strictEqual(evalContext.current_date, evalContext.today);
+                return evalContext;
+            },
+        });
+
+        this.data.foo.fields.birthday = { string: "Birthday", type: 'date' };
+        this.data.foo.records[0].birthday = "1997-01-08";
+        this.data.foo.records[1].birthday = "1997-01-09";
+        this.data.foo.records[2].birthday = "1997-01-10";
+
+        const list = await createView({
+            arch: `
+                <tree>
+                    <field name="birthday" decoration-danger="birthday > today"/>
+                </tree>`,
+            data: this.data,
+            model: 'foo',
+            View: ListView,
+        });
+
+        assert.containsOnce(list, ".o_data_row .text-danger");
+
+        list.destroy();
+        unpatchDate();
+        testUtils.mock.unpatch(BasicModel);
+    });
+
+    QUnit.test("Datetime in evaluation context works with datetime field", async function (assert) {
+        assert.expect(6);
+
+        const datetimeRegex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
+        const unpatchDate = testUtils.mock.patchDate(1997, 0, 9, 12, 0, 0);
+        testUtils.mock.patch(BasicModel, {
+            _getEvalContext() {
+                const evalContext = this._super(...arguments);
+                assert.ok(datetimeRegex.test(evalContext.now));
+                return evalContext;
+            },
+        });
+
+        /**
+         * Returns "1997-01-DD HH:MM:00" with D, H and M holding current UTC values
+         * from patched date + (deltaMinutes) minutes.
+         * This is done to allow testing from any timezone since UTC values are
+         * calculated with the offset of the current browser.
+         */
+        function dateStringDelta(deltaMinutes) {
+            const d = new Date(Date.now() + 1000 * 60 * deltaMinutes);
+            return `1997-01-${
+                String(d.getUTCDate()).padStart(2, '0')
+            } ${
+                String(d.getUTCHours()).padStart(2, '0')
+            }:${
+                String(d.getUTCMinutes()).padStart(2, '0')
+            }:00`;
+        }
+
+        // "datetime" field may collide with "datetime" object in context
+        this.data.foo.fields.birthday = { string: "Birthday", type: 'datetime' };
+        this.data.foo.records[0].birthday = dateStringDelta(-30);
+        this.data.foo.records[1].birthday = dateStringDelta(0);
+        this.data.foo.records[2].birthday = dateStringDelta(+30);
+
+        const list = await createView({
+            arch: `
+                <tree>
+                    <field name="birthday" decoration-danger="birthday > now"/>
+                </tree>`,
+            data: this.data,
+            model: 'foo',
+            View: ListView,
+        });
+
+        assert.containsOnce(list, ".o_data_row .text-danger");
+
+        list.destroy();
+        unpatchDate();
+        testUtils.mock.unpatch(BasicModel);
+    });
 });
 
 });
diff --git a/odoo/tools/view_validation.py b/odoo/tools/view_validation.py
index 6a20044f939a475fb3041243ceedb0b39ab22ff1..61202fb6b610df30e752766b6f8458414ff80b15 100644
--- a/odoo/tools/view_validation.py
+++ b/odoo/tools/view_validation.py
@@ -36,6 +36,8 @@ def _get_attrs_symbols():
         'datetime',
         'relativedelta',
         'current_date',
+        'today',
+        'now',
         'abs',
         'len',
         'bool',