Skip to content

Commit

Permalink
Extend the Timezone DBus module
Browse files Browse the repository at this point in the history
Extend the Timezone DBus module API to provide new APIs
needed by the Date and time Screen in the Web UI.

The new API will make it possible to monitor NTP service status,
query and set local system time and get a listing of valid timezones.
  • Loading branch information
M4rtinK committed Oct 4, 2023
1 parent 0a74d7b commit 8c1fffd
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 0 deletions.
45 changes: 45 additions & 0 deletions pyanaconda/modules/timezone/timezone.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 _
Expand All @@ -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__)
Expand Down Expand Up @@ -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_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?"""
Expand Down Expand Up @@ -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
)
24 changes: 24 additions & 0 deletions pyanaconda/modules/timezone/timezone_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_timezones()

@property
def IsUTC(self) -> Bool:
"""Is the hardware clock set to UTC?
Expand Down Expand Up @@ -157,3 +167,17 @@ 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)
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@
# Red Hat Author(s): Vendula Poncova <vponcova@redhat.com>
#
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

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
Expand Down Expand Up @@ -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.get_timezones()
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_timezones = MagicMock()
self.timezone_module.get_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'
)

0 comments on commit 8c1fffd

Please sign in to comment.