From 811d606d2e366598bd4a9843195123cbf1f54f69 Mon Sep 17 00:00:00 2001 From: hparfr Date: Thu, 26 Apr 2018 14:18:12 +0200 Subject: [PATCH 01/12] Add cron daylight saving time resistant --- .../README.rst | 83 +++++++++++++++++++ .../__init__.py | 1 + .../__openerp__.py | 19 +++++ .../models/__init__.py | 1 + .../models/ir_cron.py | 56 +++++++++++++ .../views/cron.xml | 13 +++ 6 files changed, 173 insertions(+) create mode 100644 cron_daylight_saving_time_resistant/README.rst create mode 100644 cron_daylight_saving_time_resistant/__init__.py create mode 100644 cron_daylight_saving_time_resistant/__openerp__.py create mode 100644 cron_daylight_saving_time_resistant/models/__init__.py create mode 100644 cron_daylight_saving_time_resistant/models/ir_cron.py create mode 100644 cron_daylight_saving_time_resistant/views/cron.xml diff --git a/cron_daylight_saving_time_resistant/README.rst b/cron_daylight_saving_time_resistant/README.rst new file mode 100644 index 000000000..6ccf4be85 --- /dev/null +++ b/cron_daylight_saving_time_resistant/README.rst @@ -0,0 +1,83 @@ +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: https://www.gnu.org/licenses/agpl + :alt: License: AGPL-3 + +=================================== +Cron daylight saving time resistant +=================================== + +This module adjust cron to run at fixed hours, local time. + + +Without this module, when a daylight saving time change occur, the cron will not take +the hour change in account. + +With this module, when a daylight saving time change occur, the offset (+1 or -1 hour) +will be applied. + + +Usage +===== + +To use this module, you need to edit a cron, and check the option, +"Daylight saving time resistant". + +#. Go to ... + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/149/10.0 + + +Known issues / Roadmap +====================== + +* Write tests +* Edge cases like run every 5 minutes + dst resistant may behave +incorrectly during the time change. + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smash it by providing detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Raphaël Reverdy https://akretion.com + +Do not contact contributors directly about support or help with technical issues. + +Funders +------- + +The development of this module has been financially supported by: + +* Akretion + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/cron_daylight_saving_time_resistant/__init__.py b/cron_daylight_saving_time_resistant/__init__.py new file mode 100644 index 000000000..9a7e03ede --- /dev/null +++ b/cron_daylight_saving_time_resistant/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/cron_daylight_saving_time_resistant/__openerp__.py b/cron_daylight_saving_time_resistant/__openerp__.py new file mode 100644 index 000000000..331eb9422 --- /dev/null +++ b/cron_daylight_saving_time_resistant/__openerp__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright <2018> +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Cron daylight saving time resistant", + "summary": "Run cron on fixed hours", + "version": "9.0.1.0.0", + "category": "Tools", + "website": "https://github.com/OCA/server-tools", + "author": "akretion, Odoo Community Association (OCA)", + "license": "AGPL-3", + "installable": True, + "depends": [ + "base", + ], + "data": [ + "views/cron.xml", + ], +} diff --git a/cron_daylight_saving_time_resistant/models/__init__.py b/cron_daylight_saving_time_resistant/models/__init__.py new file mode 100644 index 000000000..cd483538a --- /dev/null +++ b/cron_daylight_saving_time_resistant/models/__init__.py @@ -0,0 +1 @@ +from . import ir_cron \ No newline at end of file diff --git a/cron_daylight_saving_time_resistant/models/ir_cron.py b/cron_daylight_saving_time_resistant/models/ir_cron.py new file mode 100644 index 000000000..fb538f5c5 --- /dev/null +++ b/cron_daylight_saving_time_resistant/models/ir_cron.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# © 2018 Akretion - Raphaël Reverdy +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import logging +import pytz +from datetime import datetime + +from openerp import api, fields, models +from openerp.osv import fields as osv_fields +from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT +from openerp.addons.base.ir.ir_cron import _intervalTypes +_logger = logging.getLogger(__name__) + + +class IrCron(models.Model): + _inherit = "ir.cron" + + daylight_saving_time_resistant = fields.Boolean( + help="Adjust interval to run at the same hour after and before" + "daylight saving time change. It's used twice a year") + + def _process_job(cls, job_cr, job, cron_cr): + """Add or remove the Daylight saving offset when needed.""" + with api.Environment.manage(): + now = osv_fields.datetime.context_timestamp( + job_cr, job['user_id'], datetime.now()) + nextcall = osv_fields.datetime.context_timestamp( + job_cr, job['user_id'], + datetime.strptime( + job['nextcall'], + DEFAULT_SERVER_DATETIME_FORMAT) + ) + delta = _intervalTypes[job['interval_type']]( + job['interval_number']) + + if (nextcall + delta) < now: + before_offset = (nextcall + delta).utcoffset() + after_offset = now.utcoffset() + elif nextcall < now: + before_offset = nextcall.utcoffset() + after_offset = (nextcall + delta).utcoffset() + else: + before_offset = 0 + after_offset = 0 + + diff_offset = after_offset - before_offset + + if diff_offset and job['daylight_saving_time_resistant']: + numbercall = job['numbercall'] + if nextcall < now and numbercall: + nextcall -= diff_offset + modified_next_call = fields.Datetime.to_string( + nextcall.astimezone(pytz.UTC)) + job['nextcall'] = modified_next_call + super(IrCron, cls)._process_job(job_cr, job, cron_cr) diff --git a/cron_daylight_saving_time_resistant/views/cron.xml b/cron_daylight_saving_time_resistant/views/cron.xml new file mode 100644 index 000000000..3e4c802df --- /dev/null +++ b/cron_daylight_saving_time_resistant/views/cron.xml @@ -0,0 +1,13 @@ + + + + ir.cron.resist_dst + ir.cron + + + + + + + + From ee2511fce4d75339c96c75b661d5968d37d78cd4 Mon Sep 17 00:00:00 2001 From: hparfr Date: Thu, 26 Apr 2018 14:23:06 +0200 Subject: [PATCH 02/12] Add newlines at end of files --- cron_daylight_saving_time_resistant/README.rst | 2 +- cron_daylight_saving_time_resistant/__init__.py | 2 +- cron_daylight_saving_time_resistant/models/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cron_daylight_saving_time_resistant/README.rst b/cron_daylight_saving_time_resistant/README.rst index 6ccf4be85..d38395d55 100644 --- a/cron_daylight_saving_time_resistant/README.rst +++ b/cron_daylight_saving_time_resistant/README.rst @@ -26,7 +26,7 @@ To use this module, you need to edit a cron, and check the option, .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/149/10.0 + :target: https://runbot.odoo-community.org/runbot/149/9.0 Known issues / Roadmap diff --git a/cron_daylight_saving_time_resistant/__init__.py b/cron_daylight_saving_time_resistant/__init__.py index 9a7e03ede..0650744f6 100644 --- a/cron_daylight_saving_time_resistant/__init__.py +++ b/cron_daylight_saving_time_resistant/__init__.py @@ -1 +1 @@ -from . import models \ No newline at end of file +from . import models diff --git a/cron_daylight_saving_time_resistant/models/__init__.py b/cron_daylight_saving_time_resistant/models/__init__.py index cd483538a..911559262 100644 --- a/cron_daylight_saving_time_resistant/models/__init__.py +++ b/cron_daylight_saving_time_resistant/models/__init__.py @@ -1 +1 @@ -from . import ir_cron \ No newline at end of file +from . import ir_cron From e7dc0f8622ec705520a0ca082bc7a41afb1dc6af Mon Sep 17 00:00:00 2001 From: hparfr Date: Mon, 7 May 2018 10:04:49 +0200 Subject: [PATCH 03/12] Fix issue when now+delta is in future --- .../models/ir_cron.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cron_daylight_saving_time_resistant/models/ir_cron.py b/cron_daylight_saving_time_resistant/models/ir_cron.py index fb538f5c5..d16c051fb 100644 --- a/cron_daylight_saving_time_resistant/models/ir_cron.py +++ b/cron_daylight_saving_time_resistant/models/ir_cron.py @@ -34,20 +34,20 @@ class IrCron(models.Model): delta = _intervalTypes[job['interval_type']]( job['interval_number']) - if (nextcall + delta) < now: - before_offset = (nextcall + delta).utcoffset() - after_offset = now.utcoffset() - elif nextcall < now: - before_offset = nextcall.utcoffset() - after_offset = (nextcall + delta).utcoffset() - else: - before_offset = 0 - after_offset = 0 + numbercall = job['numbercall'] + future_call = nextcall + while future_call < now and numbercall: + if numbercall > 0: + numbercall -= 1 + if numbercall: + future_call += delta + + after_offset = future_call.utcoffset() + before_offset = nextcall.utcoffset() diff_offset = after_offset - before_offset if diff_offset and job['daylight_saving_time_resistant']: - numbercall = job['numbercall'] if nextcall < now and numbercall: nextcall -= diff_offset modified_next_call = fields.Datetime.to_string( From e2f2efce2c954e060ddf9993d8e97769d9218354 Mon Sep 17 00:00:00 2001 From: hparfr Date: Mon, 7 May 2018 16:52:10 +0200 Subject: [PATCH 04/12] Add tests --- .../models/ir_cron.py | 32 +++++--- .../tests/__init__.py | 1 + .../tests/test_dst.py | 81 +++++++++++++++++++ 3 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 cron_daylight_saving_time_resistant/tests/__init__.py create mode 100644 cron_daylight_saving_time_resistant/tests/test_dst.py diff --git a/cron_daylight_saving_time_resistant/models/ir_cron.py b/cron_daylight_saving_time_resistant/models/ir_cron.py index d16c051fb..c6a699640 100644 --- a/cron_daylight_saving_time_resistant/models/ir_cron.py +++ b/cron_daylight_saving_time_resistant/models/ir_cron.py @@ -20,6 +20,22 @@ class IrCron(models.Model): help="Adjust interval to run at the same hour after and before" "daylight saving time change. It's used twice a year") + def _calculate_daylight_offset(self, nextcall, delta, numbercall, now): + + tz = nextcall.tzinfo + before_offset = tz.normalize(nextcall).utcoffset() + + while nextcall < now and numbercall: + if numbercall > 0: + numbercall -= 1 + if numbercall: + nextcall += delta + + after_offset = tz.normalize(nextcall).utcoffset() + + diff_offset = after_offset - before_offset + return diff_offset + def _process_job(cls, job_cr, job, cron_cr): """Add or remove the Daylight saving offset when needed.""" with api.Environment.manage(): @@ -31,21 +47,11 @@ class IrCron(models.Model): job['nextcall'], DEFAULT_SERVER_DATETIME_FORMAT) ) + numbercall = job['numbercall'] delta = _intervalTypes[job['interval_type']]( job['interval_number']) - - numbercall = job['numbercall'] - future_call = nextcall - while future_call < now and numbercall: - if numbercall > 0: - numbercall -= 1 - if numbercall: - future_call += delta - - after_offset = future_call.utcoffset() - before_offset = nextcall.utcoffset() - - diff_offset = after_offset - before_offset + diff_offset = cls._calculate_daylight_offset( + nextcall, delta, numbercall, now) if diff_offset and job['daylight_saving_time_resistant']: if nextcall < now and numbercall: diff --git a/cron_daylight_saving_time_resistant/tests/__init__.py b/cron_daylight_saving_time_resistant/tests/__init__.py new file mode 100644 index 000000000..cec914727 --- /dev/null +++ b/cron_daylight_saving_time_resistant/tests/__init__.py @@ -0,0 +1 @@ +from . import test_dst \ No newline at end of file diff --git a/cron_daylight_saving_time_resistant/tests/test_dst.py b/cron_daylight_saving_time_resistant/tests/test_dst.py new file mode 100644 index 000000000..991d7f266 --- /dev/null +++ b/cron_daylight_saving_time_resistant/tests/test_dst.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# © 2018 Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from openerp.tests.common import TransactionCase +from datetime import datetime, timedelta +from pytz import timezone +import pytz +print "hi !" +class TestDST(TransactionCase): + print "test ?" + def test_dst(self): + """First test, caching some data.""" + cron = self.env['ir.cron'] + brux = timezone('Europe/Brussels') + ncall = -1 + winter_jan_12 = datetime(2018, 1, 1, 12, 0) + winter_feb_0 = datetime(2018, 1, 2, 0, 0) + summer_june_12 = datetime(2018, 6, 15, 12, 0) + summer_sep_3 = datetime(2018, 9, 17, 3, 0) + winter_jan_next_year = datetime(2019, 2,3, 0, 0) + tests = [{ + 'nextcall': brux.localize(winter_jan_12), + 'delta': timedelta(days=5), + 'now': brux.localize(winter_feb_0), + 'expected': timedelta(hours=0), + }, { + 'nextcall': brux.localize(winter_jan_12), + 'delta': timedelta(days=6 * 30), + 'now': brux.localize(winter_feb_0), + 'expected': timedelta(hours=1), + }, { + 'nextcall': brux.localize(winter_jan_12), + 'delta': timedelta(days=5), + 'now': brux.localize(summer_june_12), + 'expected': timedelta(hours=1), + }, { + 'nextcall': brux.localize(winter_jan_12), + 'delta': timedelta(days=6 * 30), + 'now': brux.localize(summer_june_12), + 'expected': timedelta(hours=1), + }, { + 'nextcall': brux.localize(winter_jan_12), + 'delta': timedelta(days=6 * 365), + 'now': brux.localize(winter_jan_next_year), + 'expected': timedelta(hours=0), + }] + tests = tests + [{ + 'nextcall': brux.localize(summer_june_12), + 'delta': timedelta(days=5), + 'now': brux.localize(winter_jan_next_year), + 'expected': timedelta(hours=-1), + }, { + 'nextcall': brux.localize(summer_june_12), + 'delta': timedelta(days=4 * 30), + 'now': brux.localize(winter_jan_next_year), + 'expected': timedelta(hours=-1), + }, { + 'nextcall': brux.localize(summer_june_12), + 'delta': timedelta(days=5), + 'now': brux.localize(summer_june_12), + 'expected': timedelta(hours=0), + }, { + 'nextcall': brux.localize(summer_june_12), + 'delta': timedelta(days=6 * 30), + 'now': brux.localize(summer_sep_3), + 'expected': timedelta(hours=-1), + }, { + 'nextcall': brux.localize(summer_june_12), + 'delta': timedelta(days=6 * 365), + 'now': brux.localize(summer_sep_3), + 'expected': timedelta(hours=0), + }] + test = tests[0] + for test in tests: + res = cron._calculate_daylight_offset( + test['nextcall'], + test['delta'], + ncall, + test['now']) + self.assertEqual(res, test['expected']) + From 0e2a308e92ed2af5d42b13c32928f654a2c8afed Mon Sep 17 00:00:00 2001 From: hparfr Date: Wed, 9 May 2018 17:09:21 +0200 Subject: [PATCH 05/12] Pylint --- cron_daylight_saving_time_resistant/README.rst | 2 +- cron_daylight_saving_time_resistant/tests/test_dst.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/cron_daylight_saving_time_resistant/README.rst b/cron_daylight_saving_time_resistant/README.rst index d38395d55..73b1095c3 100644 --- a/cron_daylight_saving_time_resistant/README.rst +++ b/cron_daylight_saving_time_resistant/README.rst @@ -34,7 +34,7 @@ Known issues / Roadmap * Write tests * Edge cases like run every 5 minutes + dst resistant may behave -incorrectly during the time change. + incorrectly during the time change. Bug Tracker diff --git a/cron_daylight_saving_time_resistant/tests/test_dst.py b/cron_daylight_saving_time_resistant/tests/test_dst.py index 991d7f266..fb12becfc 100644 --- a/cron_daylight_saving_time_resistant/tests/test_dst.py +++ b/cron_daylight_saving_time_resistant/tests/test_dst.py @@ -4,10 +4,10 @@ from openerp.tests.common import TransactionCase from datetime import datetime, timedelta from pytz import timezone -import pytz -print "hi !" + + class TestDST(TransactionCase): - print "test ?" + def test_dst(self): """First test, caching some data.""" cron = self.env['ir.cron'] @@ -17,7 +17,7 @@ class TestDST(TransactionCase): winter_feb_0 = datetime(2018, 1, 2, 0, 0) summer_june_12 = datetime(2018, 6, 15, 12, 0) summer_sep_3 = datetime(2018, 9, 17, 3, 0) - winter_jan_next_year = datetime(2019, 2,3, 0, 0) + winter_jan_next_year = datetime(2019, 2, 3, 0, 0) tests = [{ 'nextcall': brux.localize(winter_jan_12), 'delta': timedelta(days=5), @@ -78,4 +78,3 @@ class TestDST(TransactionCase): ncall, test['now']) self.assertEqual(res, test['expected']) - From 1358dd92d561c964a82c3a40df38802ac0952e5c Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Tue, 3 Dec 2019 13:12:40 +0100 Subject: [PATCH 06/12] Fix cron hour in case we loose an hour --- .../models/ir_cron.py | 59 ++++++++++++------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/cron_daylight_saving_time_resistant/models/ir_cron.py b/cron_daylight_saving_time_resistant/models/ir_cron.py index c6a699640..7c57d0c3f 100644 --- a/cron_daylight_saving_time_resistant/models/ir_cron.py +++ b/cron_daylight_saving_time_resistant/models/ir_cron.py @@ -38,25 +38,42 @@ class IrCron(models.Model): def _process_job(cls, job_cr, job, cron_cr): """Add or remove the Daylight saving offset when needed.""" - with api.Environment.manage(): - now = osv_fields.datetime.context_timestamp( - job_cr, job['user_id'], datetime.now()) - nextcall = osv_fields.datetime.context_timestamp( - job_cr, job['user_id'], - datetime.strptime( - job['nextcall'], - DEFAULT_SERVER_DATETIME_FORMAT) - ) - numbercall = job['numbercall'] - delta = _intervalTypes[job['interval_type']]( - job['interval_number']) - diff_offset = cls._calculate_daylight_offset( - nextcall, delta, numbercall, now) - - if diff_offset and job['daylight_saving_time_resistant']: - if nextcall < now and numbercall: - nextcall -= diff_offset - modified_next_call = fields.Datetime.to_string( - nextcall.astimezone(pytz.UTC)) - job['nextcall'] = modified_next_call super(IrCron, cls)._process_job(job_cr, job, cron_cr) + # changing the date has to be after the super, else, e may add a hour + # to next call, and the super will no run the cron, (because now will + # be 1 hour too soon) and the date will just be incremented of 1 + # hour, each hour...until the changes time really occurs... + # if need to test this, use freeze_gun lib. + if job['daylight_saving_time_resistant']: + with api.Environment.manage(): + now = osv_fields.datetime.context_timestamp( + job_cr, job['user_id'], datetime.now()) + nextcall = osv_fields.datetime.context_timestamp( + job_cr, job['user_id'], + datetime.strptime( + job['nextcall'], # original nextcall + DEFAULT_SERVER_DATETIME_FORMAT) + ) + numbercall = job['numbercall'] + delta = _intervalTypes[job['interval_type']]( + job['interval_number']) + diff_offset = cls._calculate_daylight_offset( + nextcall, delta, numbercall, now) + if diff_offset and nextcall < now and numbercall: + cron_cr.execute(""" + SELECT nextcall FROM ir_cron WHERE id = %s + """, (job['id'],)) + res_sql = cron_cr.fetchall() + new_nextcall = res_sql and res_sql[0][0] + new_nextcall = osv_fields.datetime.context_timestamp( + job_cr, job['user_id'], + datetime.strptime( + new_nextcall, # original nextcall + DEFAULT_SERVER_DATETIME_FORMAT) + ) + new_nextcall -= diff_offset + modified_next_call = fields.Datetime.to_string( + new_nextcall.astimezone(pytz.UTC)) + cron_cr.execute("UPDATE ir_cron SET nextcall=%s WHERE id=%s", + (modified_next_call, job['id'])) + cron_cr.commit() From 81a98a27e0efda7c561e6730b57b02f335199334 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Tue, 15 Feb 2022 11:49:53 +0100 Subject: [PATCH 07/12] [IMP] cron_daylight_saving_time_resistant: pre-commit execution --- .../__openerp__.py | 1 - .../models/ir_cron.py | 56 +++++--- .../tests/__init__.py | 2 +- .../tests/test_dst.py | 133 ++++++++++-------- .../views/cron.xml | 6 +- 5 files changed, 109 insertions(+), 89 deletions(-) diff --git a/cron_daylight_saving_time_resistant/__openerp__.py b/cron_daylight_saving_time_resistant/__openerp__.py index 331eb9422..9791485a7 100644 --- a/cron_daylight_saving_time_resistant/__openerp__.py +++ b/cron_daylight_saving_time_resistant/__openerp__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright <2018> # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { diff --git a/cron_daylight_saving_time_resistant/models/ir_cron.py b/cron_daylight_saving_time_resistant/models/ir_cron.py index 7c57d0c3f..3d84b311b 100644 --- a/cron_daylight_saving_time_resistant/models/ir_cron.py +++ b/cron_daylight_saving_time_resistant/models/ir_cron.py @@ -1,15 +1,15 @@ -# -*- coding: utf-8 -*- # © 2018 Akretion - Raphaël Reverdy # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). import logging -import pytz from datetime import datetime +import pytz from openerp import api, fields, models +from openerp.addons.base.ir.ir_cron import _intervalTypes from openerp.osv import fields as osv_fields from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT -from openerp.addons.base.ir.ir_cron import _intervalTypes + _logger = logging.getLogger(__name__) @@ -18,7 +18,8 @@ class IrCron(models.Model): daylight_saving_time_resistant = fields.Boolean( help="Adjust interval to run at the same hour after and before" - "daylight saving time change. It's used twice a year") + "daylight saving time change. It's used twice a year" + ) def _calculate_daylight_offset(self, nextcall, delta, numbercall, now): @@ -44,36 +45,47 @@ class IrCron(models.Model): # be 1 hour too soon) and the date will just be incremented of 1 # hour, each hour...until the changes time really occurs... # if need to test this, use freeze_gun lib. - if job['daylight_saving_time_resistant']: + if job["daylight_saving_time_resistant"]: with api.Environment.manage(): now = osv_fields.datetime.context_timestamp( - job_cr, job['user_id'], datetime.now()) - nextcall = osv_fields.datetime.context_timestamp( - job_cr, job['user_id'], - datetime.strptime( - job['nextcall'], # original nextcall - DEFAULT_SERVER_DATETIME_FORMAT) + job_cr, job["user_id"], datetime.now() ) - numbercall = job['numbercall'] - delta = _intervalTypes[job['interval_type']]( - job['interval_number']) + nextcall = osv_fields.datetime.context_timestamp( + job_cr, + job["user_id"], + datetime.strptime( + job["nextcall"], # original nextcall + DEFAULT_SERVER_DATETIME_FORMAT, + ), + ) + numbercall = job["numbercall"] + delta = _intervalTypes[job["interval_type"]](job["interval_number"]) diff_offset = cls._calculate_daylight_offset( - nextcall, delta, numbercall, now) + nextcall, delta, numbercall, now + ) if diff_offset and nextcall < now and numbercall: - cron_cr.execute(""" + cron_cr.execute( + """ SELECT nextcall FROM ir_cron WHERE id = %s - """, (job['id'],)) + """, + (job["id"],), + ) res_sql = cron_cr.fetchall() new_nextcall = res_sql and res_sql[0][0] new_nextcall = osv_fields.datetime.context_timestamp( - job_cr, job['user_id'], + job_cr, + job["user_id"], datetime.strptime( new_nextcall, # original nextcall - DEFAULT_SERVER_DATETIME_FORMAT) + DEFAULT_SERVER_DATETIME_FORMAT, + ), ) new_nextcall -= diff_offset modified_next_call = fields.Datetime.to_string( - new_nextcall.astimezone(pytz.UTC)) - cron_cr.execute("UPDATE ir_cron SET nextcall=%s WHERE id=%s", - (modified_next_call, job['id'])) + new_nextcall.astimezone(pytz.UTC) + ) + cron_cr.execute( + "UPDATE ir_cron SET nextcall=%s WHERE id=%s", + (modified_next_call, job["id"]), + ) cron_cr.commit() diff --git a/cron_daylight_saving_time_resistant/tests/__init__.py b/cron_daylight_saving_time_resistant/tests/__init__.py index cec914727..ddfe571ec 100644 --- a/cron_daylight_saving_time_resistant/tests/__init__.py +++ b/cron_daylight_saving_time_resistant/tests/__init__.py @@ -1 +1 @@ -from . import test_dst \ No newline at end of file +from . import test_dst diff --git a/cron_daylight_saving_time_resistant/tests/test_dst.py b/cron_daylight_saving_time_resistant/tests/test_dst.py index fb12becfc..4f0ff92f3 100644 --- a/cron_daylight_saving_time_resistant/tests/test_dst.py +++ b/cron_daylight_saving_time_resistant/tests/test_dst.py @@ -1,80 +1,89 @@ -# -*- coding: utf-8 -*- # © 2018 Akretion # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp.tests.common import TransactionCase from datetime import datetime, timedelta + +from openerp.tests.common import TransactionCase from pytz import timezone class TestDST(TransactionCase): - def test_dst(self): """First test, caching some data.""" - cron = self.env['ir.cron'] - brux = timezone('Europe/Brussels') + cron = self.env["ir.cron"] + brux = timezone("Europe/Brussels") ncall = -1 winter_jan_12 = datetime(2018, 1, 1, 12, 0) winter_feb_0 = datetime(2018, 1, 2, 0, 0) summer_june_12 = datetime(2018, 6, 15, 12, 0) summer_sep_3 = datetime(2018, 9, 17, 3, 0) winter_jan_next_year = datetime(2019, 2, 3, 0, 0) - tests = [{ - 'nextcall': brux.localize(winter_jan_12), - 'delta': timedelta(days=5), - 'now': brux.localize(winter_feb_0), - 'expected': timedelta(hours=0), - }, { - 'nextcall': brux.localize(winter_jan_12), - 'delta': timedelta(days=6 * 30), - 'now': brux.localize(winter_feb_0), - 'expected': timedelta(hours=1), - }, { - 'nextcall': brux.localize(winter_jan_12), - 'delta': timedelta(days=5), - 'now': brux.localize(summer_june_12), - 'expected': timedelta(hours=1), - }, { - 'nextcall': brux.localize(winter_jan_12), - 'delta': timedelta(days=6 * 30), - 'now': brux.localize(summer_june_12), - 'expected': timedelta(hours=1), - }, { - 'nextcall': brux.localize(winter_jan_12), - 'delta': timedelta(days=6 * 365), - 'now': brux.localize(winter_jan_next_year), - 'expected': timedelta(hours=0), - }] - tests = tests + [{ - 'nextcall': brux.localize(summer_june_12), - 'delta': timedelta(days=5), - 'now': brux.localize(winter_jan_next_year), - 'expected': timedelta(hours=-1), - }, { - 'nextcall': brux.localize(summer_june_12), - 'delta': timedelta(days=4 * 30), - 'now': brux.localize(winter_jan_next_year), - 'expected': timedelta(hours=-1), - }, { - 'nextcall': brux.localize(summer_june_12), - 'delta': timedelta(days=5), - 'now': brux.localize(summer_june_12), - 'expected': timedelta(hours=0), - }, { - 'nextcall': brux.localize(summer_june_12), - 'delta': timedelta(days=6 * 30), - 'now': brux.localize(summer_sep_3), - 'expected': timedelta(hours=-1), - }, { - 'nextcall': brux.localize(summer_june_12), - 'delta': timedelta(days=6 * 365), - 'now': brux.localize(summer_sep_3), - 'expected': timedelta(hours=0), - }] + tests = [ + { + "nextcall": brux.localize(winter_jan_12), + "delta": timedelta(days=5), + "now": brux.localize(winter_feb_0), + "expected": timedelta(hours=0), + }, + { + "nextcall": brux.localize(winter_jan_12), + "delta": timedelta(days=6 * 30), + "now": brux.localize(winter_feb_0), + "expected": timedelta(hours=1), + }, + { + "nextcall": brux.localize(winter_jan_12), + "delta": timedelta(days=5), + "now": brux.localize(summer_june_12), + "expected": timedelta(hours=1), + }, + { + "nextcall": brux.localize(winter_jan_12), + "delta": timedelta(days=6 * 30), + "now": brux.localize(summer_june_12), + "expected": timedelta(hours=1), + }, + { + "nextcall": brux.localize(winter_jan_12), + "delta": timedelta(days=6 * 365), + "now": brux.localize(winter_jan_next_year), + "expected": timedelta(hours=0), + }, + ] + tests = tests + [ + { + "nextcall": brux.localize(summer_june_12), + "delta": timedelta(days=5), + "now": brux.localize(winter_jan_next_year), + "expected": timedelta(hours=-1), + }, + { + "nextcall": brux.localize(summer_june_12), + "delta": timedelta(days=4 * 30), + "now": brux.localize(winter_jan_next_year), + "expected": timedelta(hours=-1), + }, + { + "nextcall": brux.localize(summer_june_12), + "delta": timedelta(days=5), + "now": brux.localize(summer_june_12), + "expected": timedelta(hours=0), + }, + { + "nextcall": brux.localize(summer_june_12), + "delta": timedelta(days=6 * 30), + "now": brux.localize(summer_sep_3), + "expected": timedelta(hours=-1), + }, + { + "nextcall": brux.localize(summer_june_12), + "delta": timedelta(days=6 * 365), + "now": brux.localize(summer_sep_3), + "expected": timedelta(hours=0), + }, + ] test = tests[0] for test in tests: res = cron._calculate_daylight_offset( - test['nextcall'], - test['delta'], - ncall, - test['now']) - self.assertEqual(res, test['expected']) + test["nextcall"], test["delta"], ncall, test["now"] + ) + self.assertEqual(res, test["expected"]) diff --git a/cron_daylight_saving_time_resistant/views/cron.xml b/cron_daylight_saving_time_resistant/views/cron.xml index 3e4c802df..66737ce16 100644 --- a/cron_daylight_saving_time_resistant/views/cron.xml +++ b/cron_daylight_saving_time_resistant/views/cron.xml @@ -1,12 +1,12 @@ - + ir.cron.resist_dst ir.cron - + - + From 8af4df7d56e75fc7b035c31faa131cb6551977d6 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Wed, 16 Feb 2022 11:47:32 +0100 Subject: [PATCH 08/12] [MIG] cron_daylight_saving_time_resistant: Migration to 14.0 --- .../README.rst | 83 ----------- .../{__openerp__.py => __manifest__.py} | 5 +- .../models/ir_cron.py | 86 ++++++----- .../readme/CONFIGURE.rst | 2 + .../readme/CONTRIBUTORS.rst | 2 + .../readme/DESCRIPTION.rst | 8 + .../tests/test_dst.py | 137 +++++++----------- .../views/{cron.xml => ir_cron_views.xml} | 3 +- 8 files changed, 111 insertions(+), 215 deletions(-) delete mode 100644 cron_daylight_saving_time_resistant/README.rst rename cron_daylight_saving_time_resistant/{__openerp__.py => __manifest__.py} (83%) create mode 100644 cron_daylight_saving_time_resistant/readme/CONFIGURE.rst create mode 100644 cron_daylight_saving_time_resistant/readme/CONTRIBUTORS.rst create mode 100644 cron_daylight_saving_time_resistant/readme/DESCRIPTION.rst rename cron_daylight_saving_time_resistant/views/{cron.xml => ir_cron_views.xml} (75%) diff --git a/cron_daylight_saving_time_resistant/README.rst b/cron_daylight_saving_time_resistant/README.rst deleted file mode 100644 index 73b1095c3..000000000 --- a/cron_daylight_saving_time_resistant/README.rst +++ /dev/null @@ -1,83 +0,0 @@ -.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png - :target: https://www.gnu.org/licenses/agpl - :alt: License: AGPL-3 - -=================================== -Cron daylight saving time resistant -=================================== - -This module adjust cron to run at fixed hours, local time. - - -Without this module, when a daylight saving time change occur, the cron will not take -the hour change in account. - -With this module, when a daylight saving time change occur, the offset (+1 or -1 hour) -will be applied. - - -Usage -===== - -To use this module, you need to edit a cron, and check the option, -"Daylight saving time resistant". - -#. Go to ... - -.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas - :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/149/9.0 - - -Known issues / Roadmap -====================== - -* Write tests -* Edge cases like run every 5 minutes + dst resistant may behave - incorrectly during the time change. - - -Bug Tracker -=========== - -Bugs are tracked on `GitHub Issues -`_. In case of trouble, please -check there if your issue has already been reported. If you spotted it first, -help us smash it by providing detailed and welcomed feedback. - -Credits -======= - -Images ------- - -* Odoo Community Association: `Icon `_. - -Contributors ------------- - -* Raphaël Reverdy https://akretion.com - -Do not contact contributors directly about support or help with technical issues. - -Funders -------- - -The development of this module has been financially supported by: - -* Akretion - -Maintainer ----------- - -.. image:: https://odoo-community.org/logo.png - :alt: Odoo Community Association - :target: https://odoo-community.org - -This module is maintained by the OCA. - -OCA, or the Odoo Community Association, is a nonprofit organization whose -mission is to support the collaborative development of Odoo features and -promote its widespread use. - -To contribute to this module, please visit https://odoo-community.org. diff --git a/cron_daylight_saving_time_resistant/__openerp__.py b/cron_daylight_saving_time_resistant/__manifest__.py similarity index 83% rename from cron_daylight_saving_time_resistant/__openerp__.py rename to cron_daylight_saving_time_resistant/__manifest__.py index 9791485a7..99cf20395 100644 --- a/cron_daylight_saving_time_resistant/__openerp__.py +++ b/cron_daylight_saving_time_resistant/__manifest__.py @@ -1,9 +1,8 @@ -# Copyright <2018> # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { "name": "Cron daylight saving time resistant", "summary": "Run cron on fixed hours", - "version": "9.0.1.0.0", + "version": "14.0.1.0.0", "category": "Tools", "website": "https://github.com/OCA/server-tools", "author": "akretion, Odoo Community Association (OCA)", @@ -13,6 +12,6 @@ "base", ], "data": [ - "views/cron.xml", + "views/ir_cron_views.xml", ], } diff --git a/cron_daylight_saving_time_resistant/models/ir_cron.py b/cron_daylight_saving_time_resistant/models/ir_cron.py index 3d84b311b..0a2c5651e 100644 --- a/cron_daylight_saving_time_resistant/models/ir_cron.py +++ b/cron_daylight_saving_time_resistant/models/ir_cron.py @@ -1,14 +1,13 @@ -# © 2018 Akretion - Raphaël Reverdy # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). import logging from datetime import datetime import pytz -from openerp import api, fields, models -from openerp.addons.base.ir.ir_cron import _intervalTypes -from openerp.osv import fields as osv_fields -from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT + +from odoo import api, fields, models + +from odoo.addons.base.models.ir_cron import _intervalTypes _logger = logging.getLogger(__name__) @@ -37,55 +36,52 @@ class IrCron(models.Model): diff_offset = after_offset - before_offset return diff_offset + @classmethod def _process_job(cls, job_cr, job, cron_cr): """Add or remove the Daylight saving offset when needed.""" - super(IrCron, cls)._process_job(job_cr, job, cron_cr) + super()._process_job(job_cr, job, cron_cr) # changing the date has to be after the super, else, e may add a hour # to next call, and the super will no run the cron, (because now will # be 1 hour too soon) and the date will just be incremented of 1 # hour, each hour...until the changes time really occurs... - # if need to test this, use freeze_gun lib. if job["daylight_saving_time_resistant"]: with api.Environment.manage(): - now = osv_fields.datetime.context_timestamp( - job_cr, job["user_id"], datetime.now() - ) - nextcall = osv_fields.datetime.context_timestamp( - job_cr, - job["user_id"], - datetime.strptime( - job["nextcall"], # original nextcall - DEFAULT_SERVER_DATETIME_FORMAT, - ), - ) - numbercall = job["numbercall"] - delta = _intervalTypes[job["interval_type"]](job["interval_number"]) - diff_offset = cls._calculate_daylight_offset( - nextcall, delta, numbercall, now - ) - if diff_offset and nextcall < now and numbercall: - cron_cr.execute( - """ - SELECT nextcall FROM ir_cron WHERE id = %s - """, - (job["id"],), - ) - res_sql = cron_cr.fetchall() - new_nextcall = res_sql and res_sql[0][0] - new_nextcall = osv_fields.datetime.context_timestamp( + try: + cron = api.Environment( job_cr, job["user_id"], - datetime.strptime( - new_nextcall, # original nextcall - DEFAULT_SERVER_DATETIME_FORMAT, - ), - ) - new_nextcall -= diff_offset - modified_next_call = fields.Datetime.to_string( - new_nextcall.astimezone(pytz.UTC) - ) - cron_cr.execute( - "UPDATE ir_cron SET nextcall=%s WHERE id=%s", - (modified_next_call, job["id"]), + {"lastcall": fields.Datetime.from_string(job["lastcall"])}, + )[cls._name] + now = fields.Datetime.context_timestamp(cron, datetime.now()) + # original nextcall + nextcall = fields.Datetime.context_timestamp(cron, job["nextcall"]) + numbercall = job["numbercall"] + delta = _intervalTypes[job["interval_type"]](job["interval_number"]) + diff_offset = cron._calculate_daylight_offset( + nextcall, delta, numbercall, now ) + if diff_offset and nextcall < now and numbercall: + cron_cr.execute( + """ + SELECT nextcall FROM ir_cron WHERE id = %s + """, + (job["id"],), + ) + res_sql = cron_cr.fetchall() + new_nextcall = res_sql and res_sql[0][0] + new_nextcall = fields.Datetime.context_timestamp( + cron, new_nextcall + ) + new_nextcall -= diff_offset + modified_next_call = fields.Datetime.to_string( + new_nextcall.astimezone(pytz.UTC) + ) + cron_cr.execute( + "UPDATE ir_cron SET nextcall=%s WHERE id=%s", + (modified_next_call, job["id"]), + ) + cron.flush() + cron.invalidate_cache() + finally: + job_cr.commit() cron_cr.commit() diff --git a/cron_daylight_saving_time_resistant/readme/CONFIGURE.rst b/cron_daylight_saving_time_resistant/readme/CONFIGURE.rst new file mode 100644 index 000000000..8fc14e7c1 --- /dev/null +++ b/cron_daylight_saving_time_resistant/readme/CONFIGURE.rst @@ -0,0 +1,2 @@ +* Go to the menu Settings => Technical => Automation => Scheduled Actions + Then you can check the check box Daylight Saving Time Resistant diff --git a/cron_daylight_saving_time_resistant/readme/CONTRIBUTORS.rst b/cron_daylight_saving_time_resistant/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..3444e7837 --- /dev/null +++ b/cron_daylight_saving_time_resistant/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Raphaël Reverdy https://akretion.com +* Florian da Costa diff --git a/cron_daylight_saving_time_resistant/readme/DESCRIPTION.rst b/cron_daylight_saving_time_resistant/readme/DESCRIPTION.rst new file mode 100644 index 000000000..c43f98341 --- /dev/null +++ b/cron_daylight_saving_time_resistant/readme/DESCRIPTION.rst @@ -0,0 +1,8 @@ +This module adjust cron to run at fixed hours, local time. + + +Without this module, when a daylight saving time change occur, the cron will not take +the hour change in account. + +With this module, when a daylight saving time change occur, the offset (+1 or -1 hour) +will be applied. diff --git a/cron_daylight_saving_time_resistant/tests/test_dst.py b/cron_daylight_saving_time_resistant/tests/test_dst.py index 4f0ff92f3..2541bfa5f 100644 --- a/cron_daylight_saving_time_resistant/tests/test_dst.py +++ b/cron_daylight_saving_time_resistant/tests/test_dst.py @@ -1,89 +1,62 @@ -# © 2018 Akretion # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from datetime import datetime, timedelta -from openerp.tests.common import TransactionCase -from pytz import timezone +from freezegun import freeze_time + +import odoo +from odoo import fields +from odoo.tests.common import TransactionCase +from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT class TestDST(TransactionCase): - def test_dst(self): - """First test, caching some data.""" - cron = self.env["ir.cron"] - brux = timezone("Europe/Brussels") - ncall = -1 - winter_jan_12 = datetime(2018, 1, 1, 12, 0) - winter_feb_0 = datetime(2018, 1, 2, 0, 0) - summer_june_12 = datetime(2018, 6, 15, 12, 0) - summer_sep_3 = datetime(2018, 9, 17, 3, 0) - winter_jan_next_year = datetime(2019, 2, 3, 0, 0) - tests = [ - { - "nextcall": brux.localize(winter_jan_12), - "delta": timedelta(days=5), - "now": brux.localize(winter_feb_0), - "expected": timedelta(hours=0), - }, - { - "nextcall": brux.localize(winter_jan_12), - "delta": timedelta(days=6 * 30), - "now": brux.localize(winter_feb_0), - "expected": timedelta(hours=1), - }, - { - "nextcall": brux.localize(winter_jan_12), - "delta": timedelta(days=5), - "now": brux.localize(summer_june_12), - "expected": timedelta(hours=1), - }, - { - "nextcall": brux.localize(winter_jan_12), - "delta": timedelta(days=6 * 30), - "now": brux.localize(summer_june_12), - "expected": timedelta(hours=1), - }, - { - "nextcall": brux.localize(winter_jan_12), - "delta": timedelta(days=6 * 365), - "now": brux.localize(winter_jan_next_year), - "expected": timedelta(hours=0), - }, - ] - tests = tests + [ - { - "nextcall": brux.localize(summer_june_12), - "delta": timedelta(days=5), - "now": brux.localize(winter_jan_next_year), - "expected": timedelta(hours=-1), - }, - { - "nextcall": brux.localize(summer_june_12), - "delta": timedelta(days=4 * 30), - "now": brux.localize(winter_jan_next_year), - "expected": timedelta(hours=-1), - }, - { - "nextcall": brux.localize(summer_june_12), - "delta": timedelta(days=5), - "now": brux.localize(summer_june_12), - "expected": timedelta(hours=0), - }, - { - "nextcall": brux.localize(summer_june_12), - "delta": timedelta(days=6 * 30), - "now": brux.localize(summer_sep_3), - "expected": timedelta(hours=-1), - }, - { - "nextcall": brux.localize(summer_june_12), - "delta": timedelta(days=6 * 365), - "now": brux.localize(summer_sep_3), - "expected": timedelta(hours=0), - }, - ] - test = tests[0] - for test in tests: - res = cron._calculate_daylight_offset( - test["nextcall"], test["delta"], ncall, test["now"] + def setUp(self): + super().setUp() + self.registry.enter_test_mode(self.env.cr) + + def tearDown(self): + self.registry.leave_test_mode() + super().tearDown() + + def _check_cron_date_after_run(self, cron, datetime_str): + # add 10 sec to make sure cron will run + datetime_current = datetime.strptime( + datetime_str, DEFAULT_SERVER_DATETIME_FORMAT + ) + timedelta(seconds=10) + datetime_current_str = datetime_current.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + with freeze_time(datetime_current_str): + cron.write( + {"nextcall": datetime_str, "daylight_saving_time_resistant": True} ) - self.assertEqual(res, test["expected"]) + cron.flush() + self.env.cr.execute("SELECT * FROM ir_cron WHERE id = %s", (cron.id,)) + job = self.env.cr.dictfetchall()[0] + timezone_date_orig = fields.Datetime.context_timestamp(cron, cron.nextcall) + with odoo.registry(self.env.cr.dbname).cursor() as new_cr: + registry = odoo.registry(new_cr.dbname) + registry["ir.cron"]._process_job(new_cr, job, new_cr) + day_after_date_orig = (timezone_date_orig + timedelta(days=1)).day + timezone_date_after = fields.Datetime.context_timestamp(cron, cron.nextcall) + # check the cron is really planned the next day (which mean it has run + # then check the planned hour is the same even in case of change of time + # (brussels summer time/ brussels winter time + self.assertEqual(day_after_date_orig, timezone_date_after.day) + self.assertEqual(timezone_date_orig.hour, timezone_date_after.hour) + + def test_cron(self): + cron = self.env["ir.cron"].create( + { + "name": "TestCron", + "model_id": self.env.ref("base.model_res_partner").id, + "state": "code", + "code": "model.search([])", + "interval_number": 1, + "interval_type": "days", + "numbercall": -1, + "doall": False, + } + ) + # from summer time to winter time + self._check_cron_date_after_run(cron, "2021-10-30 15:00:00") + # from winter time to summer time + self._check_cron_date_after_run(cron, "2021-03-27 15:00:00") diff --git a/cron_daylight_saving_time_resistant/views/cron.xml b/cron_daylight_saving_time_resistant/views/ir_cron_views.xml similarity index 75% rename from cron_daylight_saving_time_resistant/views/cron.xml rename to cron_daylight_saving_time_resistant/views/ir_cron_views.xml index 66737ce16..0f79894dd 100644 --- a/cron_daylight_saving_time_resistant/views/cron.xml +++ b/cron_daylight_saving_time_resistant/views/ir_cron_views.xml @@ -1,9 +1,8 @@ - ir.cron.resist_dst ir.cron - + From 9b9224a5b482c6a13b068d928f846e329a607d67 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Tue, 13 Dec 2022 17:29:37 +0100 Subject: [PATCH 09/12] [IMP] cron_daylight_saving_time_resistant: black, isort, prettier --- .../odoo/addons/cron_daylight_saving_time_resistant | 1 + setup/cron_daylight_saving_time_resistant/setup.py | 6 ++++++ 2 files changed, 7 insertions(+) create mode 120000 setup/cron_daylight_saving_time_resistant/odoo/addons/cron_daylight_saving_time_resistant create mode 100644 setup/cron_daylight_saving_time_resistant/setup.py diff --git a/setup/cron_daylight_saving_time_resistant/odoo/addons/cron_daylight_saving_time_resistant b/setup/cron_daylight_saving_time_resistant/odoo/addons/cron_daylight_saving_time_resistant new file mode 120000 index 000000000..61296a7d3 --- /dev/null +++ b/setup/cron_daylight_saving_time_resistant/odoo/addons/cron_daylight_saving_time_resistant @@ -0,0 +1 @@ +../../../../cron_daylight_saving_time_resistant \ No newline at end of file diff --git a/setup/cron_daylight_saving_time_resistant/setup.py b/setup/cron_daylight_saving_time_resistant/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/cron_daylight_saving_time_resistant/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) From 86df925ff9c8a39322c6d941e2a95ca0982837e5 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Tue, 13 Dec 2022 17:41:31 +0100 Subject: [PATCH 10/12] [16.0][MIG] Migrate cron_daylight_saving_time_resistant to v16 --- .../README.rst | 87 ++++ .../__manifest__.py | 2 +- .../models/ir_cron.py | 10 +- .../static/description/index.html | 432 ++++++++++++++++++ .../tests/test_dst.py | 13 +- 5 files changed, 535 insertions(+), 9 deletions(-) create mode 100644 cron_daylight_saving_time_resistant/README.rst create mode 100644 cron_daylight_saving_time_resistant/static/description/index.html diff --git a/cron_daylight_saving_time_resistant/README.rst b/cron_daylight_saving_time_resistant/README.rst new file mode 100644 index 000000000..82709ce50 --- /dev/null +++ b/cron_daylight_saving_time_resistant/README.rst @@ -0,0 +1,87 @@ +=================================== +Cron daylight saving time resistant +=================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github + :target: https://github.com/OCA/server-tools/tree/16.0/cron_daylight_saving_time_resistant + :alt: OCA/server-tools +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-tools-16-0/server-tools-16-0-cron_daylight_saving_time_resistant + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/webui/builds.html?repo=OCA/server-tools&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adjust cron to run at fixed hours, local time. + + +Without this module, when a daylight saving time change occur, the cron will not take +the hour change in account. + +With this module, when a daylight saving time change occur, the offset (+1 or -1 hour) +will be applied. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +* Go to the menu Settings => Technical => Automation => Scheduled Actions + Then you can check the check box Daylight Saving Time Resistant + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* akretion + +Contributors +~~~~~~~~~~~~ + +* Raphaël Reverdy https://akretion.com +* Florian da Costa + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/server-tools `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/cron_daylight_saving_time_resistant/__manifest__.py b/cron_daylight_saving_time_resistant/__manifest__.py index 99cf20395..856aca84e 100644 --- a/cron_daylight_saving_time_resistant/__manifest__.py +++ b/cron_daylight_saving_time_resistant/__manifest__.py @@ -2,7 +2,7 @@ { "name": "Cron daylight saving time resistant", "summary": "Run cron on fixed hours", - "version": "14.0.1.0.0", + "version": "16.0.1.0.0", "category": "Tools", "website": "https://github.com/OCA/server-tools", "author": "akretion, Odoo Community Association (OCA)", diff --git a/cron_daylight_saving_time_resistant/models/ir_cron.py b/cron_daylight_saving_time_resistant/models/ir_cron.py index 0a2c5651e..62273647a 100644 --- a/cron_daylight_saving_time_resistant/models/ir_cron.py +++ b/cron_daylight_saving_time_resistant/models/ir_cron.py @@ -37,15 +37,15 @@ class IrCron(models.Model): return diff_offset @classmethod - def _process_job(cls, job_cr, job, cron_cr): + def _process_job(cls, db, cron_cr, job): """Add or remove the Daylight saving offset when needed.""" - super()._process_job(job_cr, job, cron_cr) + res = super()._process_job(db, cron_cr, job) # changing the date has to be after the super, else, e may add a hour # to next call, and the super will no run the cron, (because now will # be 1 hour too soon) and the date will just be incremented of 1 # hour, each hour...until the changes time really occurs... if job["daylight_saving_time_resistant"]: - with api.Environment.manage(): + with cls.pool.cursor() as job_cr: try: cron = api.Environment( job_cr, @@ -80,8 +80,6 @@ class IrCron(models.Model): "UPDATE ir_cron SET nextcall=%s WHERE id=%s", (modified_next_call, job["id"]), ) - cron.flush() - cron.invalidate_cache() finally: - job_cr.commit() cron_cr.commit() + return res diff --git a/cron_daylight_saving_time_resistant/static/description/index.html b/cron_daylight_saving_time_resistant/static/description/index.html new file mode 100644 index 000000000..6caa93991 --- /dev/null +++ b/cron_daylight_saving_time_resistant/static/description/index.html @@ -0,0 +1,432 @@ + + + + + + +Cron daylight saving time resistant + + + +
+

Cron daylight saving time resistant

+ + +

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

+

This module adjust cron to run at fixed hours, local time.

+

Without this module, when a daylight saving time change occur, the cron will not take +the hour change in account.

+

With this module, when a daylight saving time change occur, the offset (+1 or -1 hour) +will be applied.

+

Table of contents

+ +
+

Configuration

+
    +
  • Go to the menu Settings => Technical => Automation => Scheduled Actions +Then you can check the check box Daylight Saving Time Resistant
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • akretion
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/server-tools project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/cron_daylight_saving_time_resistant/tests/test_dst.py b/cron_daylight_saving_time_resistant/tests/test_dst.py index 2541bfa5f..75c16c513 100644 --- a/cron_daylight_saving_time_resistant/tests/test_dst.py +++ b/cron_daylight_saving_time_resistant/tests/test_dst.py @@ -28,13 +28,19 @@ class TestDST(TransactionCase): cron.write( {"nextcall": datetime_str, "daylight_saving_time_resistant": True} ) - cron.flush() + cron.flush_recordset() self.env.cr.execute("SELECT * FROM ir_cron WHERE id = %s", (cron.id,)) job = self.env.cr.dictfetchall()[0] timezone_date_orig = fields.Datetime.context_timestamp(cron, cron.nextcall) + # ensure Paris time zone is taken into account. If we only work in UTC + # there is not change of hour and the test will be green even if it does + # nothing at all... + self.assertEqual(timezone_date_orig.tzinfo.zone, "Europe/Paris") with odoo.registry(self.env.cr.dbname).cursor() as new_cr: registry = odoo.registry(new_cr.dbname) - registry["ir.cron"]._process_job(new_cr, job, new_cr) + registry["ir.cron"]._process_job(new_cr.dbname, new_cr, job) + # since it is updated as a sql query in module + cron.invalidate_recordset() day_after_date_orig = (timezone_date_orig + timedelta(days=1)).day timezone_date_after = fields.Datetime.context_timestamp(cron, cron.nextcall) # check the cron is really planned the next day (which mean it has run @@ -44,6 +50,9 @@ class TestDST(TransactionCase): self.assertEqual(timezone_date_orig.hour, timezone_date_after.hour) def test_cron(self): + user = self.env.ref("base.user_root") + user.write({"tz": "Europe/Paris"}) + user.invalidate_recordset() cron = self.env["ir.cron"].create( { "name": "TestCron", From af8f577ddb0bfe9c26d1b104ffe557bab4f9841d Mon Sep 17 00:00:00 2001 From: clementmbr Date: Wed, 24 Jan 2024 11:40:39 -0300 Subject: [PATCH 11/12] [FIX] test cron_daylight_saving_time_resistant --- cron_daylight_saving_time_resistant/tests/test_dst.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cron_daylight_saving_time_resistant/tests/test_dst.py b/cron_daylight_saving_time_resistant/tests/test_dst.py index 75c16c513..25ab5d81b 100644 --- a/cron_daylight_saving_time_resistant/tests/test_dst.py +++ b/cron_daylight_saving_time_resistant/tests/test_dst.py @@ -38,7 +38,9 @@ class TestDST(TransactionCase): self.assertEqual(timezone_date_orig.tzinfo.zone, "Europe/Paris") with odoo.registry(self.env.cr.dbname).cursor() as new_cr: registry = odoo.registry(new_cr.dbname) - registry["ir.cron"]._process_job(new_cr.dbname, new_cr, job) + db = odoo.sql_db.db_connect(new_cr.dbname) + + registry["ir.cron"]._process_job(db, new_cr, job) # since it is updated as a sql query in module cron.invalidate_recordset() day_after_date_orig = (timezone_date_orig + timedelta(days=1)).day From fa3dfab394ac464965dd17a24d858f93fe04e207 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Mon, 29 Jan 2024 09:55:32 +0100 Subject: [PATCH 12/12] cron_daylight_saving_time_resistant : add maintainers --- cron_daylight_saving_time_resistant/__manifest__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cron_daylight_saving_time_resistant/__manifest__.py b/cron_daylight_saving_time_resistant/__manifest__.py index 856aca84e..07f6c8bd6 100644 --- a/cron_daylight_saving_time_resistant/__manifest__.py +++ b/cron_daylight_saving_time_resistant/__manifest__.py @@ -8,6 +8,7 @@ "author": "akretion, Odoo Community Association (OCA)", "license": "AGPL-3", "installable": True, + "maintainers": ["florian-dacosta"], "depends": [ "base", ],