From e531d8ed185b7e0da77be190e65d5f53a279cfd9 Mon Sep 17 00:00:00 2001 From: Xavier-Do Date: Thu, 28 Mar 2024 13:55:15 +0100 Subject: [PATCH] [IMP] support deprecated timezone resolution In ubuntu noble, some timezone where removed leading to errors when trying to assign/access them. This was partially fixed in the code by removing all references to old timezones but one issue remains: if a database contains timezones that are not defined in the os, the resolution will fail and break at runtime This patches proposes to alter timezone to fallback on the new canonical timezone if the timezone was removed. This list was generated by checking all symlink in /usr/share/zoneinfo in ubuntu 22.04 that disapeared in ubuntu 24.04 This solutions will work when moving a database from one server to another, even without migration. The all_timezone is not modified on purpose to avoid breaking existing logic. This list may be used to define if a timezone is known by postgress, define selection fiels, .... we don"t want to increase the list in those case. Some other logic using all_timezone may need to be updated but This will be done in master. Part-of: odoo/odoo#160842 --- odoo/addons/base/tests/test_tz.py | 57 +++++++++---- odoo/tools/__init__.py | 1 + odoo/tools/_monkeypatches_pytz.py | 132 ++++++++++++++++++++++++++++++ odoo/tools/safe_eval.py | 1 + 4 files changed, 174 insertions(+), 17 deletions(-) create mode 100644 odoo/tools/_monkeypatches_pytz.py diff --git a/odoo/addons/base/tests/test_tz.py b/odoo/addons/base/tests/test_tz.py index 741a3575ea2..be3ca5ce011 100644 --- a/odoo/addons/base/tests/test_tz.py +++ b/odoo/addons/base/tests/test_tz.py @@ -1,33 +1,56 @@ +import datetime import pytz +from unittest.mock import patch from odoo.tests.common import TransactionCase +from odoo.tools._monkeypatches_pytz import _tz_mapping class TestTZ(TransactionCase): def test_tz_legacy(self): - #See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + d = datetime.datetime(1969, 7, 16) + # See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones def assertTZEqual(tz1, tz2): - self.assertEqual(tz1._utcoffset, tz2._utcoffset) - self.assertEqual(tz1._transition_info, tz2._transition_info) + self.assertEqual(tz1.localize(d).strftime('%z'), tz2.localize(d).strftime('%z')) - assertTZEqual(pytz.timezone('US/Eastern'), pytz.timezone('America/New_York')) - assertTZEqual(pytz.timezone('US/Central'), pytz.timezone('America/Chicago')) - assertTZEqual(pytz.timezone('US/Mountain'), pytz.timezone('America/Denver')) - assertTZEqual(pytz.timezone('US/Pacific'), pytz.timezone('America/Los_Angeles')) - assertTZEqual(pytz.timezone('US/Alaska'), pytz.timezone('America/Anchorage')) - assertTZEqual(pytz.timezone('US/Hawaii'), pytz.timezone('Pacific/Honolulu')) - assertTZEqual(pytz.timezone('Canada/Atlantic'), pytz.timezone('America/Halifax')) - assertTZEqual(pytz.timezone('Canada/Pacific'), pytz.timezone('America/Vancouver')) - assertTZEqual(pytz.timezone('Mexico/BajaNorte'), pytz.timezone('America/Tijuana')) - assertTZEqual(pytz.timezone('Mexico/General'), pytz.timezone('America/Mexico_City')) - assertTZEqual(pytz.timezone('Brazil/East'), pytz.timezone('America/Sao_Paulo')) - # This one is not correct for a strange reason - #assertTZEqual(pytz.timezone('Pacific/Midway'), pytz.timezone('Pacific/Pago_Pago')) + # in some version of tzdata the timezones are not symlink, as an example in 2023c-0ubuntu0.20.04.1 + # this as a side effect to have sligh difference in timezones seconds, breaking the following assertions + # in some cases: + # + # self.assertEqual(tz1._utcoffset, tz2._utcoffset) + # if hasattr(tz2, '_transition_info'): + # self.assertEqual(tz1._transition_info, tz2._transition_info) + # + # the first one is more robust + + for source, target in _tz_mapping.items(): + with self.subTest(source=source, target=target): + if source == 'Pacific/Enderbury': # this one was wrong in some version of tzdata + continue + assertTZEqual(pytz.timezone(source), pytz.timezone(target)) + + def test_dont_adapt_available_tz(self): + with patch.dict(_tz_mapping, { + 'DeprecatedUtc': 'UTC', + 'America/New_York': 'UTC', + }): + self.assertNotIn('DeprecatedUtc', pytz.all_timezones_set, 'DeprecatedUtc is not available') + self.assertEqual(pytz.timezone('DeprecatedUtc'), pytz.timezone('UTC'), 'DeprecatedUtc does not exist and should have been replaced with UTC') + self.assertIn('America/New_York', pytz.all_timezones_set, 'America/New_York is available') + self.assertNotEqual(pytz.timezone('America/New_York'), pytz.timezone('UTC'), 'America/New_York exists and should not have been replaced with UTC') def test_cannot_set_deprecated_timezone(self): # this should be ok self.env.user.tz = "America/New_York" if "US/Eastern" not in pytz.all_timezones: with self.assertRaises(ValueError): - self.env.user.tz = "US/Eastern" \ No newline at end of file + self.env.user.tz = "US/Eastern" + + def test_partner_with_old_tz(self): + # this test makes sence after ubuntu noble without tzdata-legacy installed + partner = self.env['res.partner'].create({'name': 'test', 'tz': 'UTC'}) + self.env.cr.execute("""UPDATE res_partner set tz='US/Eastern' WHERE id=%s""", (partner.id,)) + partner.invalidate_recordset() + self.assertEqual(partner.tz, 'US/Eastern') # tz was update despite selection not existing, but data was not migrated + self.assertEqual(partner.tz_offset, '-0400', "We don't expect pytz.timezone to fail if the timezone diseapeared when chaging os version") diff --git a/odoo/tools/__init__.py b/odoo/tools/__init__.py index 7150927960d..e1bf6c8da74 100644 --- a/odoo/tools/__init__.py +++ b/odoo/tools/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import _monkeypatches +from . import _monkeypatches_pytz from werkzeug import urls if not hasattr(urls, 'url_join'): diff --git a/odoo/tools/_monkeypatches_pytz.py b/odoo/tools/_monkeypatches_pytz.py new file mode 100644 index 00000000000..b1414dc5227 --- /dev/null +++ b/odoo/tools/_monkeypatches_pytz.py @@ -0,0 +1,132 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. +""" +In ubuntu noble, some timezone where removed leading to errors when trying to assign/access them. + +This was partially fixed in the code by removing all references to old timezones but one issue remains: +if a database contains timezones that are not defined in the os, the resolution will fail and break +at runtime. + +This patches proposes to alter timezone to fallback on the new canonical timezone if the timezone was removed. + +This list was generated by checking all symlink in /usr/share/zoneinfo in ubuntu 22.04 that disapeared in ubuntu 24.04 + +This solutions will work when moving a database from one server to another, even without migration. +This list could be improved for other purposes. + +""" + +import pytz + +_tz_mapping = { + "Africa/Asmera": "Africa/Nairobi", + "America/Argentina/ComodRivadavia": "America/Argentina/Catamarca", + "America/Buenos_Aires": "America/Argentina/Buenos_Aires", + "America/Cordoba": "America/Argentina/Cordoba", + "America/Fort_Wayne": "America/Indiana/Indianapolis", + "America/Indianapolis": "America/Indiana/Indianapolis", + "America/Jujuy": "America/Argentina/Jujuy", + "America/Knox_IN": "America/Indiana/Knox", + "America/Louisville": "America/Kentucky/Louisville", + "America/Mendoza": "America/Argentina/Mendoza", + "America/Rosario": "America/Argentina/Cordoba", + "Antarctica/South_Pole": "Pacific/Auckland", + "Asia/Ashkhabad": "Asia/Ashgabat", + "Asia/Calcutta": "Asia/Kolkata", + "Asia/Chungking": "Asia/Shanghai", + "Asia/Dacca": "Asia/Dhaka", + "Asia/Katmandu": "Asia/Kathmandu", + "Asia/Macao": "Asia/Macau", + "Asia/Rangoon": "Asia/Yangon", + "Asia/Saigon": "Asia/Ho_Chi_Minh", + "Asia/Thimbu": "Asia/Thimphu", + "Asia/Ujung_Pandang": "Asia/Makassar", + "Asia/Ulan_Bator": "Asia/Ulaanbaatar", + "Atlantic/Faeroe": "Atlantic/Faroe", + "Australia/ACT": "Australia/Sydney", + "Australia/LHI": "Australia/Lord_Howe", + "Australia/North": "Australia/Darwin", + "Australia/NSW": "Australia/Sydney", + "Australia/Queensland": "Australia/Brisbane", + "Australia/South": "Australia/Adelaide", + "Australia/Tasmania": "Australia/Hobart", + "Australia/Victoria": "Australia/Melbourne", + "Australia/West": "Australia/Perth", + "Brazil/Acre": "America/Rio_Branco", + "Brazil/DeNoronha": "America/Noronha", + "Brazil/East": "America/Sao_Paulo", + "Brazil/West": "America/Manaus", + "Canada/Atlantic": "America/Halifax", + "Canada/Central": "America/Winnipeg", + "Canada/Eastern": "America/Toronto", + "Canada/Mountain": "America/Edmonton", + "Canada/Newfoundland": "America/St_Johns", + "Canada/Pacific": "America/Vancouver", + "Canada/Saskatchewan": "America/Regina", + "Canada/Yukon": "America/Whitehorse", + "Chile/Continental": "America/Santiago", + "Chile/EasterIsland": "Pacific/Easter", + "Cuba": "America/Havana", + "Egypt": "Africa/Cairo", + "Eire": "Europe/Dublin", + "Europe/Kiev": "Europe/Kyiv", + "Europe/Uzhgorod": "Europe/Kyiv", + "Europe/Zaporozhye": "Europe/Kyiv", + "GB": "Europe/London", + "GB-Eire": "Europe/London", + "GMT+0": "Etc/GMT", + "GMT-0": "Etc/GMT", + "GMT0": "Etc/GMT", + "Greenwich": "Etc/GMT", + "Hongkong": "Asia/Hong_Kong", + "Iceland": "Africa/Abidjan", + "Iran": "Asia/Tehran", + "Israel": "Asia/Jerusalem", + "Jamaica": "America/Jamaica", + "Japan": "Asia/Tokyo", + "Kwajalein": "Pacific/Kwajalein", + "Libya": "Africa/Tripoli", + "Mexico/BajaNorte": "America/Tijuana", + "Mexico/BajaSur": "America/Mazatlan", + "Mexico/General": "America/Mexico_City", + "Navajo": "America/Denver", + "NZ": "Pacific/Auckland", + "NZ-CHAT": "Pacific/Chatham", + "Pacific/Enderbury": "Pacific/Kanton", + "Pacific/Ponape": "Pacific/Guadalcanal", + "Pacific/Truk": "Pacific/Port_Moresby", + "Poland": "Europe/Warsaw", + "Portugal": "Europe/Lisbon", + "PRC": "Asia/Shanghai", + "ROC": "Asia/Taipei", + "ROK": "Asia/Seoul", + "Singapore": "Asia/Singapore", + "Turkey": "Europe/Istanbul", + "UCT": "Etc/UTC", + "Universal": "Etc/UTC", + "US/Alaska": "America/Anchorage", + "US/Aleutian": "America/Adak", + "US/Arizona": "America/Phoenix", + "US/Central": "America/Chicago", + "US/Eastern": "America/New_York", + "US/East-Indiana": "America/Indiana/Indianapolis", + "US/Hawaii": "Pacific/Honolulu", + "US/Indiana-Starke": "America/Indiana/Knox", + "US/Michigan": "America/Detroit", + "US/Mountain": "America/Denver", + "US/Pacific": "America/Los_Angeles", + "US/Samoa": "Pacific/Pago_Pago", + "W-SU": "Europe/Moscow", + "Zulu": "Etc/UTC", +} + + +original_pytz_timezone = pytz.timezone + + +def timezone(name): + if name not in pytz.all_timezones_set and name in _tz_mapping: + name = _tz_mapping[name] + return original_pytz_timezone(name) + + +pytz.timezone = timezone diff --git a/odoo/tools/safe_eval.py b/odoo/tools/safe_eval.py index aae06ee4b66..72590f73d1a 100644 --- a/odoo/tools/safe_eval.py +++ b/odoo/tools/safe_eval.py @@ -65,6 +65,7 @@ _UNSAFE_ATTRIBUTES = { 'cr_await', 'cr_code', 'cr_frame', # Coroutine generators 'ag_await', 'ag_code', 'ag_frame', + }