Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cylc/flow/rundb.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,9 @@ def close(self) -> None:
def connect(self) -> sqlite3.Connection:
"""Connect to the database."""
if self.conn is None:
self.conn = sqlite3.connect(self.db_file_name, self.CONN_TIMEOUT)
self.conn = sqlite3.connect(
self.db_file_name, timeout=self.CONN_TIMEOUT
)
return self.conn

def create_tables(self):
Expand Down
88 changes: 31 additions & 57 deletions cylc/flow/wallclock.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from calendar import timegm
from datetime import datetime, timedelta, timezone
from typing import Optional, Tuple

from metomi.isodatetime.timezone import (
get_local_time_zone_format, get_local_time_zone, TimeZoneFormatMode)
Expand All @@ -42,10 +43,7 @@
TIME_ZONE_STRING_LOCAL_EXTENDED = get_local_time_zone_format(
TimeZoneFormatMode.extended)
TIME_ZONE_STRING_UTC = "Z"
TIME_ZONE_UTC_UTC_OFFSET = (0, 0)
TIME_ZONE_LOCAL_UTC_OFFSET = get_local_time_zone()
TIME_ZONE_LOCAL_UTC_OFFSET_HOURS = TIME_ZONE_LOCAL_UTC_OFFSET[0]
TIME_ZONE_LOCAL_UTC_OFFSET_MINUTES = TIME_ZONE_LOCAL_UTC_OFFSET[1]

TIME_ZONE_LOCAL_INFO = {
"hours": TIME_ZONE_LOCAL_UTC_OFFSET[0],
Expand All @@ -55,8 +53,8 @@
}

TIME_ZONE_UTC_INFO = {
"hours": TIME_ZONE_UTC_UTC_OFFSET[0],
"minutes": TIME_ZONE_UTC_UTC_OFFSET[1],
"hours": 0,
"minutes": 0,
"string_basic": TIME_ZONE_STRING_UTC,
"string_extended": TIME_ZONE_STRING_UTC
}
Expand All @@ -74,8 +72,9 @@
_FLAGS['utc_mode'] = bool(mode)


def now(override_use_utc=None):
"""Return a current-time datetime.datetime and a UTC timezone flag.
def now(override_use_utc: Optional[bool] = None) -> Tuple[datetime, bool]:
"""Return a current-time, timezone-aware datetime.datetime and a flag
indicating whether it is UTC or not.

Keyword arguments:
override_use_utc (default None) - a boolean (or None) that, if
Expand All @@ -85,9 +84,9 @@

"""
if override_use_utc or (override_use_utc is None and _FLAGS['utc_mode']):
return datetime.utcnow(), False
return datetime.now(timezone.utc), False
else:
return datetime.now(), True
return datetime.now().astimezone(), True


def get_current_time_string(display_sub_seconds=False, override_use_utc=None,
Expand All @@ -113,9 +112,13 @@
use_basic_format=use_basic_format)


def get_time_string(date_time, display_sub_seconds=False,
override_use_utc=None, use_basic_format=False,
date_time_is_local=False, custom_time_zone_info=None):
def get_time_string(
date_time: datetime,
display_sub_seconds: bool = False,
override_use_utc: Optional[bool] = None,
use_basic_format: bool = False,
date_time_is_local: bool = False,
):
"""Return a string representing the current system time.

Arguments:
Expand All @@ -133,46 +136,20 @@
most useful for filenames where ":" may cause problems.
date_time_is_local - a boolean that, if True, indicates that
the date_time argument object is in the local time zone, not UTC.
custom_time_zone_info (default None) - a dictionary that enforces
a particular time zone. It looks like {"hours": _hours,
"minutes": _minutes, "string": _string} where _hours and _minutes
are the hours and minutes offset from UTC and _string is the string
to use as the time zone designator.

"""
time_zone_string = None
if custom_time_zone_info is not None:
custom_hours = custom_time_zone_info["hours"]
custom_minutes = custom_time_zone_info["minutes"]
if use_basic_format:
custom_string = custom_time_zone_info["string_basic"]
else:
custom_string = custom_time_zone_info["string_extended"]
if date_time_is_local:
date_time_hours = TIME_ZONE_LOCAL_UTC_OFFSET_HOURS
date_time_minutes = TIME_ZONE_LOCAL_UTC_OFFSET_MINUTES
else:
date_time_hours, date_time_minutes = (0, 0)
diff_hours = custom_hours - date_time_hours
diff_minutes = custom_minutes - date_time_minutes
date_time = date_time + timedelta(
hours=diff_hours, minutes=diff_minutes)
time_zone_string = custom_string
Comment on lines -144 to -160
Copy link
Member Author

@MetRonnie MetRonnie Apr 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hjoliver, this PR removes a code block that isn't used any more. Can you have a quick look at it and merge if ok.

This code block was previously only used by the Cylc 7 GUI (7.8.15):

# Task not finished, but has started and has a meant;
# so we can compute an expected time of completion.
tetc_string = (
self._id_tetc_cache.get(id_, {}).get(tetc_unix))
if tetc_string is None:
# We have to calculate it.
tetc_string = get_time_string_from_unix_time(
tetc_unix,
custom_time_zone_info=time_zone_info)

However, even then it looks like it was only used for the same purpose that is already provided by override_use_utc:

time_zone_info = self.updater.global_summary.get("time zone info")

if get_utc_mode():
global_summary['time zone info'] = TIME_ZONE_UTC_INFO
else:
global_summary['time zone info'] = TIME_ZONE_LOCAL_INFO

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hjoliver poke

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hjoliver poke

elif override_use_utc or (override_use_utc is None and _FLAGS['utc_mode']):
if override_use_utc or (override_use_utc is None and _FLAGS['utc_mode']):
time_zone_string = TIME_ZONE_STRING_UTC
if date_time_is_local:
date_time = date_time - timedelta(
hours=TIME_ZONE_LOCAL_UTC_OFFSET_HOURS,
minutes=TIME_ZONE_LOCAL_UTC_OFFSET_MINUTES
)
h, m = TIME_ZONE_LOCAL_UTC_OFFSET
date_time = date_time - timedelta(hours=h, minutes=m)

Check warning on line 145 in cylc/flow/wallclock.py

View check run for this annotation

Codecov / codecov/patch

cylc/flow/wallclock.py#L144-L145

Added lines #L144 - L145 were not covered by tests
else:
if use_basic_format:
time_zone_string = TIME_ZONE_STRING_LOCAL_BASIC
else:
time_zone_string = TIME_ZONE_STRING_LOCAL_EXTENDED
if not date_time_is_local:
diff_hours = TIME_ZONE_LOCAL_UTC_OFFSET_HOURS
diff_minutes = TIME_ZONE_LOCAL_UTC_OFFSET_MINUTES
diff_hours, diff_minutes = TIME_ZONE_LOCAL_UTC_OFFSET
date_time = date_time + timedelta(
hours=diff_hours, minutes=diff_minutes)
if use_basic_format:
Expand All @@ -187,9 +164,11 @@
return date_time_string + time_zone_string


def get_time_string_from_unix_time(unix_time, display_sub_seconds=False,
use_basic_format=False,
custom_time_zone_info=None):
def get_time_string_from_unix_time(
unix_time: float,
display_sub_seconds: bool = False,
use_basic_format: bool = False,
) -> str:
"""Convert a unix timestamp into a local time zone datetime.datetime.

Arguments:
Expand All @@ -202,20 +181,15 @@
use_basic_format (default False) - a boolean that, if True,
represents the date/time without "-" or ":" delimiters. This is
most useful for filenames where ":" may cause problems.
custom_time_zone_info (default None) - a dictionary that enforces
a particular time zone. It looks like {"hours": _hours,
"minutes": _minutes, "string": _string} where _hours and _minutes
are the hours and minutes offset from UTC and _string is the string
to use as the time zone designator.

"""
date_time = datetime.fromtimestamp(unix_time, timezone.utc)
return get_time_string(date_time,
display_sub_seconds=display_sub_seconds,
use_basic_format=use_basic_format,
override_use_utc=None,
date_time_is_local=False,
custom_time_zone_info=custom_time_zone_info)
return get_time_string(
datetime.fromtimestamp(unix_time, timezone.utc),
display_sub_seconds=display_sub_seconds,
use_basic_format=use_basic_format,
override_use_utc=None,
date_time_is_local=False,
)


def get_unix_time_from_time_string(datetime_string):
Expand Down
9 changes: 6 additions & 3 deletions tests/unit/cycling/test_iso8601.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from datetime import datetime
from datetime import (
datetime,
timezone,
)

import pytest
from pytest import param
Expand Down Expand Up @@ -892,10 +895,10 @@ def test_next_simple_no_now(set_cycling_type):
point = "next(T00Z)+P1D"
output = ingest_time(point, my_now)

current_time = datetime.utcnow()
current_time = datetime.now(timezone.utc)
# my_now is None, but ingest_time will have used a similar time, and
# the returned value must be after current_time
output_time = datetime.strptime(output, "%Y%m%dT%H%MZ")
output_time = datetime.strptime(output, "%Y%m%dT%H%M%z")
assert current_time < output_time


Expand Down
37 changes: 35 additions & 2 deletions tests/unit/test_wallclock.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import pytest
from datetime import (
datetime,
timedelta,
timezone,
)

from metomi.isodatetime.data import CALENDAR
from cylc.flow.wallclock import get_unix_time_from_time_string
import pytest

from cylc.flow.wallclock import (
get_time_string,
get_unix_time_from_time_string,
)


@pytest.mark.parametrize(
Expand Down Expand Up @@ -60,3 +69,27 @@ def test_get_unix_time_from_time_string_360(time_str, time_sec):
def test_get_unix_time_from_time_string_error(value, error):
with pytest.raises(error):
get_unix_time_from_time_string(value)


@pytest.mark.parametrize('tz_info', [
pytest.param(None, id="naive"),
pytest.param(timezone.utc, id="utc-tz-aware"),
pytest.param(timezone(timedelta(hours=5)), id="custom-tz-aware"),
])
def test_get_time_string_tzinfo(tz_info, monkeypatch: pytest.MonkeyPatch):
"""Basic check it handles naive and timezone-aware datetime objects.

Currently we just ignore the timezone information in the datetime object.
"""
# Mock UTC time zone:
monkeypatch.setattr(
'cylc.flow.wallclock.TIME_ZONE_LOCAL_UTC_OFFSET', (0, 0)
)
for fmt in ('BASIC', 'EXTENDED'):
monkeypatch.setattr(
f'cylc.flow.wallclock.TIME_ZONE_STRING_LOCAL_{fmt}', 'Z'
)

assert get_time_string(
datetime(2077, 2, 8, 13, 42, 39, 123456, tz_info)
) == '2077-02-08T13:42:39Z'
5 changes: 3 additions & 2 deletions tests/unit/tui/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@

from datetime import (
datetime,
timedelta
timedelta,
timezone
)
from unittest.mock import Mock

Expand Down Expand Up @@ -171,7 +172,7 @@ def test_get_task_icon(
start_time = None
if start_offset is not None:
start_time = get_time_string(
datetime.utcnow() - timedelta(seconds=start_offset)
datetime.now(timezone.utc) - timedelta(seconds=start_offset)
)
assert (
(
Expand Down
Loading