diff --git a/pyanaconda/modules/timezone/timezone.py b/pyanaconda/modules/timezone/timezone.py index b514455e6cee..4c7ec64cd572 100644 --- a/pyanaconda/modules/timezone/timezone.py +++ b/pyanaconda/modules/timezone/timezone.py @@ -17,6 +17,9 @@ # License and may only be used or replicated with the express permission of # Red Hat, Inc. # + +import datetime + from pykickstart.errors import KickstartParseError from pyanaconda.core.i18n import _ @@ -36,6 +39,7 @@ ConfigureNTPTask, ConfigureTimezoneTask from pyanaconda.modules.timezone.kickstart import TimezoneKickstartSpecification from pyanaconda.modules.timezone.timezone_interface import TimezoneInterface +from pyanaconda.timezone import get_timezone, set_system_date_time, get_all_regions_and_timezones from pyanaconda.anaconda_loggers import get_module_logger log = get_module_logger(__name__) @@ -167,6 +171,20 @@ def set_timezone_with_priority(self, timezone, priority): self.timezone_changed.emit() log.debug("Timezone is set to %s.", timezone) + def get_all_valid_timezones(self): + """Get all valid timezones. + + :return: list of valid timezones + :rtype: list of str + """ + timezone_dict = get_all_regions_and_timezones() + # convert to a dict of lists for easier transfer over DBus + # - change the nested sets to lists + new_timezone_dict = {} + for region in timezone_dict: + new_timezone_dict[region] = list(timezone_dict[region]) + return new_timezone_dict + @property def is_utc(self): """Is the hardware clock set to UTC?""" @@ -259,3 +277,30 @@ def geolocation_result(self): :return GeolocationData: result of the lookup, empty if not ready yet """ return self._geoloc_result + + def get_system_date_time(self): + """Get system time as a ISO 8601 formatted string. + + :return: system time as ISO 8601 formatted string + :rtype: str + """ + # convert to the expected tzinfo format via get_timezone() + return datetime.datetime.now(get_timezone(self._timezone)).isoformat() + + def set_system_date_time(self, date_time_spec): + """Set system time based on a ISO 8601 formatted string. + + :param str date_time_spec: ISO 8601 time specification to use + """ + log.debug("Setting system time to: %s, with timezone: %s", date_time_spec, self._timezone) + # first convert the ISO 8601 time string to a Python date object + date = datetime.datetime.fromisoformat(date_time_spec) + # set the date to the system + set_system_date_time( + year=date.year, + month=date.month, + day=date.day, + hour=date.hour, + minute=date.minute, + tz=self._timezone + ) diff --git a/pyanaconda/modules/timezone/timezone_interface.py b/pyanaconda/modules/timezone/timezone_interface.py index 25d7b16226eb..506e48b8b1a7 100644 --- a/pyanaconda/modules/timezone/timezone_interface.py +++ b/pyanaconda/modules/timezone/timezone_interface.py @@ -79,6 +79,16 @@ def SetTimezoneWithPriority(self, timezone: Str, priority: UInt16): """ self.implementation.set_timezone_with_priority(timezone, priority) + def GetTimezones(self) -> Dict[Str, List[Str]]: + """Get valid timezones. + + Return a dictionary, where keys are region ids and values are lists + of timezone names in the region. + + :return: a dictionary of timezone lists per region + """ + return self.implementation.get_all_valid_timezones() + @property def IsUTC(self) -> Bool: """Is the hardware clock set to UTC? @@ -157,3 +167,21 @@ def GeolocationResult(self) -> Structure: return GeolocationData.to_structure( self.implementation.geolocation_result ) + + def GetSystemDateTime(self) -> Str: + """Get the current local date and time of the system. + + The timezone set via the Timezone property affects the returned data. + + :return: a string representing the date and time in ISO 8601 format + """ + return self.implementation.get_system_date_time() + + def SetSystemDateTime(self, date_time_spec: Str): + """Set the current local date and time of the system. + + The timezone set via the Timezone property will be applied to the received data. + + :param date_time_spec: a string representing the date and time in ISO 8601 format + """ + self.implementation.set_system_date_time(date_time_spec) diff --git a/tests/unit_tests/pyanaconda_tests/modules/timezone/test_module_timezone.py b/tests/unit_tests/pyanaconda_tests/modules/timezone/test_module_timezone.py index 462e2b2898cf..7f8bdb19ca1a 100644 --- a/tests/unit_tests/pyanaconda_tests/modules/timezone/test_module_timezone.py +++ b/tests/unit_tests/pyanaconda_tests/modules/timezone/test_module_timezone.py @@ -18,6 +18,9 @@ # Red Hat Author(s): Vendula Poncova # import unittest +from unittest.mock import patch, MagicMock + +from collections import OrderedDict from dasbus.structure import compare_data from dasbus.typing import * # pylint: disable=wildcard-import @@ -25,6 +28,7 @@ from pyanaconda.core.constants import TIME_SOURCE_SERVER, TIME_SOURCE_POOL, \ TIMEZONE_PRIORITY_DEFAULT, TIMEZONE_PRIORITY_LANGUAGE, TIMEZONE_PRIORITY_GEOLOCATION, \ TIMEZONE_PRIORITY_KICKSTART, TIMEZONE_PRIORITY_USER +from pyanaconda.timezone import get_timezone from pyanaconda.modules.common.constants.services import TIMEZONE from pyanaconda.modules.common.structures.requirement import Requirement from pyanaconda.modules.common.structures.timezone import TimeSourceData @@ -378,3 +382,62 @@ def test_geoloc_result_callback(self): result = GeolocationData.from_values(territory="", timezone="") self.timezone_module._set_geolocation_result(result) assert self.timezone_module.geolocation_result == result + + @patch("pyanaconda.modules.timezone.timezone.get_all_regions_and_timezones") + def test_get_timezones(self, get_all_tz): + """Test getting a listing of all valid timezones.""" + get_all_tz.return_value = OrderedDict([("foo", {"bar", "baz"})]) + # as the timezones for a region are listed as a set, we need to be a bit careful + # when comparing the results + result = self.timezone_module.GetTimezones() + assert list(result.keys()) == ["foo"] + assert sorted(result["foo"]) == ['bar', 'baz'] + get_all_tz.assert_called_once() + + @patch("pyanaconda.modules.timezone.timezone.datetime") + def test_get_system_date_time(self, fake_datetime): + """Test getting system date and time.""" + # use a non-default timezone + self.timezone_module._timezone = "Antarctica/South_Pole" + # fake date object returned by now() call + fake_date = MagicMock() + fake_date.isoformat.return_value = "2023-06-22T18:49:36.878200" + + def fake_now(value): + assert value == get_timezone("Antarctica/South_Pole") + return fake_date + + fake_datetime.datetime.now.side_effect = fake_now + assert self.timezone_module.get_system_date_time() == "2023-06-22T18:49:36.878200" + fake_date.isoformat.assert_called_once() + + @patch("pyanaconda.modules.timezone.timezone.set_system_date_time") + def test_set_system_date_time(self, fake_set_time): + """Test setting system date and time.""" + self.timezone_module.set_system_date_time("2023-06-22T18:49:36.878200") + fake_set_time.assert_called_once_with(year=2023, + month=6, + day=22, + hour=18, + minute=49, + tz="America/New_York") + + def test_get_timezones_interface(self): + """Test the GetTimezones interface method.""" + self.timezone_module.get_all_valid_timezones = MagicMock() + self.timezone_module.get_all_valid_timezones.return_value = {"foo": ["bar", "baz"]} + assert self.timezone_interface.GetTimezones() == {"foo": ["bar", "baz"]} + + def test_get_system_date_time_interface(self): + """Test the GetSystemDateTime interface method.""" + self.timezone_module.get_system_date_time = MagicMock() + self.timezone_module.get_system_date_time.return_value = "2023-06-22T18:49:36.878200" + assert self.timezone_interface.GetSystemDateTime() == "2023-06-22T18:49:36.878200" + + def test_set_system_date_time_interface(self): + """Test the SetSystemDateTime interface method.""" + self.timezone_module.set_system_date_time = MagicMock() + self.timezone_interface.SetSystemDateTime("2023-06-22T18:49:36.878200") + self.timezone_module.set_system_date_time.assert_called_once_with( + '2023-06-22T18:49:36.878200' + ) diff --git a/ui/webui/test/reference b/ui/webui/test/reference index 094d24845711..2d8a30a4cf42 160000 --- a/ui/webui/test/reference +++ b/ui/webui/test/reference @@ -1 +1 @@ -Subproject commit 094d248457119ead6c5c07f833e7b985789b27df +Subproject commit 2d8a30a4cf4291d6225d9ce80e1260558eb85246