[FIX] *: adapt tz for Ubuntu Noble

Some of the non canononical timezones are not present in Ubuntu Noble,
it would be a better practice to only use canonical timezones in data
and tests.

Note that this is not a real fix for all cases since the database that
ran on Ubuntu Jammy and are moved to an ubuntu Noble server will have
the issue with timezones already in database.

One of the possible fix would be to manage that during upgrades, but
this isn't a verry flexible solution since upgrade are meant to manage
chyange of version, not change of server. If an old 17.0 versions needs
to be moved to a Noble server, this won't work.

Another solution would be to install package like tzdata-legacy that may
keep the old timezones but it is not the only think since TAI-10 are
also in this package. This solution is not ideal because non canonical
timezone will still be shown in the dropdown. We would need to filter
them.

A last solution would be to add the support for those old timezones by
monkeypatching the lib. This way, only new timezones would be shown but
non canonical one won't crash when used. This is not ideal either
because we may need to keep this for a while. But in combination with
the upgrade solution, it may work proprely.

Part-of: odoo/odoo#160842
This commit is contained in:
Xavier-Do
2024-04-07 10:03:48 +00:00
parent 0dc85a2200
commit e9b62b47dc
16 changed files with 73 additions and 39 deletions
@@ -529,14 +529,14 @@ class RecurrenceRule(models.Model):
# Given the following recurrence:
# - monthly
# - 1st of each month
# - timezone US/Eastern (UTC−05:00)
# - at 6am US/Eastern = 11am UTC
# - timezone America/New_York (UTC−05:00)
# - at 6am America/New_York = 11am UTC
# - from 2019/02/01 to 2019/05/01.
# The naive way would be to store:
# 2019/02/01 11:00 - 2019/03/01 11:00 - 2019/04/01 11:00 - 2019/05/01 11:00 (UTC)
#
# But a DST change occurs on 2019/03/10 in US/Eastern timezone. US/Eastern is now UTC−04:00.
# From this point in time, 11am (UTC) is actually converted to 7am (US/Eastern) instead of the expected 6am!
# But a DST change occurs on 2019/03/10 in America/New_York timezone. America/New_York is now UTC−04:00.
# From this point in time, 11am (UTC) is actually converted to 7am (America/New_York) instead of the expected 6am!
# What should be stored is:
# 2019/02/01 11:00 - 2019/03/01 11:00 - 2019/04/01 10:00 - 2019/05/01 10:00 (UTC)
# ***** *****
+3 -3
View File
@@ -218,14 +218,14 @@ class TestCalendar(SavepointCaseWithUserDemo):
self.assertEqual(d.minute, 30)
def test_recurring_ny(self):
self.user_demo.tz = 'US/Eastern'
self.user_demo.tz = 'America/New_York'
event = self.CalendarEvent.create({'user_id': self.user_demo.id, 'name': 'test', 'partner_ids': [Command.link(self.user_demo.partner_id.id)]})
f = Form(event.with_context(tz='US/Eastern').with_user(self.user_demo))
f = Form(event.with_context(tz='America/New_York').with_user(self.user_demo))
f.name = 'test'
f.start = '2022-07-07 01:00:00' # This is in UTC. In NY, it corresponds to the 6th of july at 9pm.
f.recurrency = True
self.assertEqual(f.weekday, 'WED')
self.assertEqual(f.event_tz, 'US/Eastern', "The value should correspond to the user tz")
self.assertEqual(f.event_tz, 'America/New_York', "The value should correspond to the user tz")
self.assertEqual(f.count, 1, "The default value should be displayed")
self.assertEqual(f.interval, 1, "The default value should be displayed")
self.assertEqual(f.month_by, "date", "The default value should be displayed")
@@ -216,7 +216,7 @@ class TestCreateRecurrentEvents(TestRecurrentEvents):
'rrule_type': 'weekly',
'mon': True,
'count': '2',
'event_tz': 'US/Eastern', # DST change on 2002/10/27
'event_tz': 'America/New_York', # DST change on 2002/10/27
})
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
self.assertEventDates(recurrence.calendar_event_ids, [
@@ -226,9 +226,9 @@ class TestCreateRecurrentEvents(TestRecurrentEvents):
def test_ambiguous_dst_time_winter(self):
""" Test hours stays the same, regardless of DST changes """
eastern = pytz.timezone('US/Eastern')
eastern = pytz.timezone('America/New_York')
dt = eastern.localize(datetime(2002, 10, 20, 1, 30, 00)).astimezone(pytz.utc).replace(tzinfo=None)
# Next occurence happens at 1:30am on 27th Oct 2002 which happened twice in the US/Eastern
# Next occurence happens at 1:30am on 27th Oct 2002 which happened twice in the America/New_York
# timezone when the clocks where put back at the end of Daylight Saving Time
self.event.start = dt
self.event.stop = dt + relativedelta(hours=1)
@@ -237,7 +237,7 @@ class TestCreateRecurrentEvents(TestRecurrentEvents):
'rrule_type': 'weekly',
'sun': True,
'count': '2',
'event_tz': 'US/Eastern' # DST change on 2002/4/7
'event_tz': 'America/New_York' # DST change on 2002/4/7
})
events = self.event.recurrence_id.calendar_event_ids
self.assertEqual(events.mapped('duration'), [1, 1])
@@ -248,10 +248,10 @@ class TestCreateRecurrentEvents(TestRecurrentEvents):
def test_ambiguous_dst_time_spring(self):
""" Test hours stays the same, regardless of DST changes """
eastern = pytz.timezone('US/Eastern')
eastern = pytz.timezone('America/New_York')
dt = eastern.localize(datetime(2002, 3, 31, 2, 30, 00)).astimezone(pytz.utc).replace(tzinfo=None)
# Next occurence happens 2:30am on 7th April 2002 which never happened at all in the
# US/Eastern timezone, as the clocks where put forward at 2:00am skipping the entire hour
# America/New_York timezone, as the clocks where put forward at 2:00am skipping the entire hour
self.event.start = dt
self.event.stop = dt + relativedelta(hours=1)
self.event._apply_recurrence_values({
@@ -259,7 +259,7 @@ class TestCreateRecurrentEvents(TestRecurrentEvents):
'rrule_type': 'weekly',
'sun': True,
'count': '2',
'event_tz': 'US/Eastern' # DST change on 2002/4/7
'event_tz': 'America/New_York' # DST change on 2002/4/7
})
events = self.event.recurrence_id.calendar_event_ids
self.assertEqual(events.mapped('duration'), [1, 1])
+2 -2
View File
@@ -15,7 +15,7 @@
<field name="seats_limited">True</field>
<field name="seats_max">50</field>
<field name="address_id" ref="event.res_partner_location_2"/>
<field name="date_tz">US/Pacific</field>
<field name="date_tz">America/Los_Angeles</field>
<field name="event_type_id" ref="event_type_0"/>
<field name="stage_id" ref="event_stage_booked"/>
<field name="tag_ids" eval="[(4, ref('event.event_tag_category_1_tag_1')), (4, ref('event.event_tag_category_2_tag_1'))]"/>
@@ -196,7 +196,7 @@
<field name="seats_limited">True</field>
<field name="seats_max">4</field>
<field name="address_id" ref="event.res_partner_location_2"/>
<field name="date_tz">US/Pacific</field>
<field name="date_tz">America/Los_Angeles</field>
<field name="event_type_id" ref="event_type_1"/>
<field name="stage_id" ref="event_stage_done"/>
<field name="kanban_state">done</field>
+1 -1
View File
@@ -12,7 +12,7 @@
</record>
<record id="event_type_2" model="event.type">
<field name="name">Sport</field>
<field name="default_timezone">US/Pacific</field>
<field name="default_timezone">America/Los_Angeles</field>
<field name="sequence">5</field>
</record>
+2 -2
View File
@@ -84,7 +84,7 @@ class TestEventData(TestEventInternalsCommon):
event.write({
'date_begin': datetime(2020, 1, 31, 14, 30, 0),
'date_end': datetime(2020, 2, 1, 2, 0, 0),
'date_tz': 'Mexico/General'
'date_tz': 'America/Mexico_City'
})
self.assertTrue(event.is_one_day)
@@ -96,7 +96,7 @@ class TestEventData(TestEventInternalsCommon):
event.write({
'date_begin': datetime(2020, 1, 31, 14, 30, 0),
'date_end': datetime(2020, 2, 1, 2, 0, 0),
'date_tz': 'Mexico/General'
'date_tz': 'America/Mexico_City'
})
self.assertTrue(event.is_one_day)
self.assertFalse(event.is_ongoing)
+2 -2
View File
@@ -179,7 +179,7 @@ class TestSelfAccessRights(TestHrCommon):
self.env['ir.config_parameter'].set_param('hr.hr_employee_self_edit', False)
# should not raise
vals = [
{'tz': "Australia/ACT"},
{'tz': "Australia/Sydney"},
{'email': "new@example.com"},
{'signature': "<p>I'm Richard!</p>"},
{'notification_type': "email"},
@@ -193,7 +193,7 @@ class TestSelfAccessRights(TestHrCommon):
# they are in SELF_READABLE_FIELDS
self.env['ir.config_parameter'].set_param('hr.hr_employee_self_edit', False)
vals = [
{'tz': "Australia/ACT"},
{'tz': "Australia/Sydney"},
{'email': "new@example.com"},
{'signature': "<p>I'm Richard!</p>"},
{'notification_type': "email"},
@@ -111,7 +111,7 @@ class TestGlobalLeaves(TestHrHolidaysCommon):
"""
calendar_asia = self.env['resource.calendar'].create({
'name': 'Asia calendar',
'tz': 'Asia/Calcutta', # UTC +05:30
'tz': 'Asia/Kolkata', # UTC +05:30
'hours_per_day': 8.0,
'attendance_ids': []
})
@@ -124,12 +124,12 @@ class TestGlobalLeaves(TestHrHolidaysCommon):
})
# Expectation:
# 6:00:00 in UTC (data from the browser) --> 8:00:00 for Europe/Brussel (UTC +02:00)
# 8:00:00 for Asia/Calcutta (UTC +05:30) --> 2:30:00 in UTC
# 8:00:00 for Asia/Kolkata (UTC +05:30) --> 2:30:00 in UTC
self.assertEqual(global_leave.date_from, datetime(2023, 5, 15, 2, 30))
self.assertEqual(global_leave.date_to, datetime(2023, 5, 15, 11, 30))
# Note:
# The user in Europe/Brussels timezone see 4:30 and not 2:30 because he is in UTC +02:00.
# The user in Asia/Calcutta timezone (determined via the browser) see 8:00 because he is in UTC +05:30
# The user in Asia/Kolkata timezone (determined via the browser) see 8:00 because he is in UTC +05:30
def test_global_leave_number_of_days_with_new(self):
"""
@@ -256,7 +256,7 @@ class TestLeaveRequests(TestHrHolidaysCommon):
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_timezone_employee_leave_request(self):
""" Create a leave request for an employee in another timezone """
self.employee_emp.tz = 'NZ' # GMT+12
self.employee_emp.tz = 'Pacific/Auckland' # GMT+12
leave = self.env['hr.leave'].new({
'employee_id': self.employee_emp.id,
'holiday_status_id': self.holidays_type_1.id,
@@ -273,7 +273,7 @@ class TestLeaveRequests(TestHrHolidaysCommon):
def test_timezone_company_leave_request(self):
""" Create a leave request for a company in another timezone """
company = self.env['res.company'].create({'name': "Hergé"})
company.resource_calendar_id.tz = 'NZ' # GMT+12
company.resource_calendar_id.tz = 'Australia/Sydney' # GMT+12
leave = self.env['hr.leave'].new({
'employee_id': self.employee_emp.id,
'holiday_status_id': self.holidays_type_1.id,
@@ -291,7 +291,7 @@ class TestLeaveRequests(TestHrHolidaysCommon):
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_timezone_company_validated(self):
""" Create a leave request for a company in another timezone and validate it """
self.env.user.tz = 'NZ' # GMT+12
self.env.user.tz = 'Australia/Sydney' # GMT+12
company = self.env['res.company'].create({'name': "Hergé"})
employee = self.env['hr.employee'].create({'name': "Remi", 'company_id': company.id})
leave_form = Form(self.env['hr.leave'], view='hr_holidays.hr_leave_view_form_manager')
@@ -313,7 +313,7 @@ class TestLeaveRequests(TestHrHolidaysCommon):
def test_timezone_department_leave_request(self):
""" Create a leave request for a department in another timezone """
company = self.env['res.company'].create({'name': "Hergé"})
company.resource_calendar_id.tz = 'NZ' # GMT+12
company.resource_calendar_id.tz = 'Australia/Sydney' # GMT+12
department = self.env['hr.department'].create({'name': "Museum", 'company_id': company.id})
leave = self.env['hr.leave'].new({
'employee_id': self.employee_emp.id,
@@ -470,7 +470,7 @@ class TestLeaveRequests(TestHrHolidaysCommon):
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_leave_defaults_with_timezones(self):
""" Make sure that leaves start with correct defaults for non-UTC timezones """
timezones_to_test = ('UTC', 'Pacific/Midway', 'US/Pacific', 'Asia/Taipei', 'Pacific/Kiritimati') # UTC, UTC -11, UTC -8, UTC +8, UTC +14
timezones_to_test = ('UTC', 'Pacific/Midway', 'America/Los_Angeles', 'Asia/Taipei', 'Pacific/Kiritimati') # UTC, UTC -11, UTC -8, UTC +8, UTC +14
# January 2020
# Su Mo Tu We Th Fr Sa
@@ -117,7 +117,7 @@ class TestCurrencyRates(TransactionCase):
"rate": CAD_UTC,
}
)
self.env.user.tz = "Australia/ACT"
self.env.user.tz = "Australia/Sydney"
self.env["res.currency.rate"].create(
{
"currency_id": cad.id,
+1 -1
View File
@@ -305,7 +305,7 @@ class TestActivityMixin(TestActivityCommon):
name='user Australia',
login='user Australia',
)
cls.user_australia.tz = 'Australia/ACT'
cls.user_australia.tz = 'Australia/Sydney'
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_activity_mixin(self):
+1 -1
View File
@@ -60,7 +60,7 @@ class TestResourceCommon(TransactionCase):
(0, 0, 0, '1', 'line_section', 10), (8, 16, 0, '1', False, 11), (7, 15, 2, '1', False, 12),
(8, 16, 3, '1', False, 13), (10, 16, 4, '1', False, 14)], 'Europe/Brussels')
cls.calendar_paul = cls._define_calendar('Morning and evening shifts', sum([((2, 7, i, 0.5), (10, 16, i, 0.5)) for i in range(5)], ()), 'Brazil/DeNoronha')
cls.calendar_paul = cls._define_calendar('Morning and evening shifts', sum([((2, 7, i, 0.5), (10, 16, i, 0.5)) for i in range(5)], ()), 'America/Noronha')
cls.calendar_bob = cls._define_calendar('Calendar with adjacent attendances', sum([((8, 12, i, 0.5), (12, 16, i, 0.5)) for i in range(5)], ()), 'Europe/Brussels')
+5 -5
View File
@@ -651,16 +651,16 @@ class TestResMixin(TestResourceCommon):
def test_adjust_calendar_timezone_before(self):
# Calendar:
# Every day 8-16
self.jean.tz = 'Japan'
self.jean.tz = 'Asia/Tokyo'
self.calendar_jean.tz = 'Europe/Brussels'
result = self.jean._adjust_to_calendar(
datetime_tz(2020, 4, 1, 0, 0, 0, tzinfo='Japan'),
datetime_tz(2020, 4, 1, 23, 59, 59, tzinfo='Japan'),
datetime_tz(2020, 4, 1, 0, 0, 0, tzinfo='Asia/Tokyo'),
datetime_tz(2020, 4, 1, 23, 59, 59, tzinfo='Asia/Tokyo'),
)
self.assertEqual(result[self.jean], (
datetime_tz(2020, 4, 1, 8, 0, 0, tzinfo='Japan'),
datetime_tz(2020, 4, 1, 16, 0, 0, tzinfo='Japan'),
datetime_tz(2020, 4, 1, 8, 0, 0, tzinfo='Asia/Tokyo'),
datetime_tz(2020, 4, 1, 16, 0, 0, tzinfo='Asia/Tokyo'),
), "It should have found a starting time the 1st")
def test_adjust_calendar_timezone_after(self):
+1
View File
@@ -46,6 +46,7 @@ from . import test_search
from . import test_split_table
from . import test_sql
from . import test_translate
from . import test_tz
# from . import test_uninstall # loop
from . import test_user_has_group
from . import test_views
+2 -2
View File
@@ -291,7 +291,7 @@ class TestFormatLangDate(TransactionCase):
# -- test `time`
time_part = datetime.time(16, 30, 22)
time_part_tz = datetime.time(16, 30, 22, tzinfo=pytz.timezone('US/Eastern')) # 4:30 PM timezoned
time_part_tz = datetime.time(16, 30, 22, tzinfo=pytz.timezone('America/New_York')) # 4:30 PM timezoned
self.assertEqual(misc.format_time(lang.with_context(lang='fr_FR').env, time_part, time_format='HH:mm:ss'), '16:30:22')
self.assertEqual(misc.format_time(lang.with_context(lang='zh_CN').env, time_part, time_format="ah:m:ss"), '\u4e0b\u53484:30:22')
@@ -306,7 +306,7 @@ class TestFormatLangDate(TransactionCase):
#Check timezone conversion in format_time
self.assertEqual(misc.format_time(lang.with_context(lang='fr_FR').env, datetime_str, 'Europe/Brussels', time_format='HH:mm:ss Z'), '11:33:00 +0100')
self.assertEqual(misc.format_time(lang.with_context(lang='fr_FR').env, datetime_str, 'US/Eastern', time_format='HH:mm:ss Z'), '05:33:00 -0500')
self.assertEqual(misc.format_time(lang.with_context(lang='fr_FR').env, datetime_str, 'America/New_York', time_format='HH:mm:ss Z'), '05:33:00 -0500')
# Check given `lang_code` overwites context lang
self.assertEqual(misc.format_time(lang.with_context(lang='fr_FR').env, time_part, time_format='ah:mm', lang_code='zh_CN'), '\u4e0b\u53484:30')
+33
View File
@@ -0,0 +1,33 @@
import pytz
from odoo.tests.common import TransactionCase
class TestTZ(TransactionCase):
def test_tz_legacy(self):
#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)
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'))
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"