Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

timer: import submodules only and add tests #1075

Merged
merged 4 commits into from
Jan 12, 2025
Merged
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
59 changes: 31 additions & 28 deletions lib/timer.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,56 @@
"""A timer for use in lichess-bot."""
import time
import datetime

from datetime import datetime, timedelta
from time import perf_counter
from typing import Optional


def msec(time_in_msec: float) -> datetime.timedelta:
def msec(time_in_msec: float) -> timedelta:
"""Create a timedelta duration in milliseconds."""
return datetime.timedelta(milliseconds=time_in_msec)
return timedelta(milliseconds=time_in_msec)


def to_msec(duration: datetime.timedelta) -> float:
def to_msec(duration: timedelta) -> float:
"""Return a bare number representing the length of the duration in milliseconds."""
return duration / msec(1)


def msec_str(duration: datetime.timedelta) -> str:
def msec_str(duration: timedelta) -> str:
"""Return a string with the duration value in whole number milliseconds."""
return str(round(to_msec(duration)))


def seconds(time_in_sec: float) -> datetime.timedelta:
def seconds(time_in_sec: float) -> timedelta:
"""Create a timedelta duration in seconds."""
return datetime.timedelta(seconds=time_in_sec)
return timedelta(seconds=time_in_sec)


def to_seconds(duration: datetime.timedelta) -> float:
def to_seconds(duration: timedelta) -> float:
"""Return a bare number representing the length of the duration in seconds."""
return duration.total_seconds()


def sec_str(duration: datetime.timedelta) -> str:
def sec_str(duration: timedelta) -> str:
"""Return a string with the duration value in whole number seconds."""
return str(round(to_seconds(duration)))


def minutes(time_in_minutes: float) -> datetime.timedelta:
def minutes(time_in_minutes: float) -> timedelta:
"""Create a timedelta duration in minutes."""
return datetime.timedelta(minutes=time_in_minutes)
return timedelta(minutes=time_in_minutes)


def hours(time_in_hours: float) -> datetime.timedelta:
def hours(time_in_hours: float) -> timedelta:
"""Create a timedelta duration in hours."""
return datetime.timedelta(hours=time_in_hours)
return timedelta(hours=time_in_hours)


def days(time_in_days: float) -> datetime.timedelta:
def days(time_in_days: float) -> timedelta:
"""Create a timedelta duration in days."""
return datetime.timedelta(days=time_in_days)
return timedelta(days=time_in_days)


def years(time_in_years: float) -> datetime.timedelta:
def years(time_in_years: float) -> timedelta:
"""Create a timedelta duration in median years--i.e., 365 days."""
return days(365) * time_in_years

Expand All @@ -68,36 +69,38 @@ class Timer:
the timer was created or since it was last reset.
"""

def __init__(self, duration: datetime.timedelta = seconds(0),
backdated_timestamp: Optional[datetime.datetime] = None) -> None:
__slots__ = ["duration", "starting_time"]

def __init__(self, duration: timedelta = seconds(0),
backdated_timestamp: Optional[datetime] = None) -> None:
"""
Start the timer.

:param duration: The duration of time before Timer.is_expired() returns True.
:param backdated_timestamp: When the timer should have started. Used to keep the timers between sessions.
"""
self.duration = duration
self.reset()
if backdated_timestamp is not None:
time_already_used = datetime.datetime.now() - backdated_timestamp
self.starting_time -= to_seconds(time_already_used)
self.starting_time = perf_counter()

if backdated_timestamp:
self.starting_time -= to_seconds(datetime.now() - backdated_timestamp)

def is_expired(self) -> bool:
"""Check if a timer is expired."""
return self.time_since_reset() >= self.duration

def reset(self) -> None:
"""Reset the timer."""
self.starting_time = time.perf_counter()
self.starting_time = perf_counter()

def time_since_reset(self) -> datetime.timedelta:
def time_since_reset(self) -> timedelta:
"""How much time has passed."""
return seconds(time.perf_counter() - self.starting_time)
return seconds(perf_counter() - self.starting_time)

def time_until_expiration(self) -> datetime.timedelta:
def time_until_expiration(self) -> timedelta:
"""How much time is left until it expires."""
return max(seconds(0), self.duration - self.time_since_reset())

def starting_timestamp(self, timestamp_format: str) -> str:
"""When the timer started."""
return (datetime.datetime.now() - self.time_since_reset()).strftime(timestamp_format)
return (datetime.now() - self.time_since_reset()).strftime(timestamp_format)
95 changes: 95 additions & 0 deletions test_bot/test_timer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""Test functions dedicated to time measurement and conversion."""

from datetime import datetime, timedelta

from lib import timer


def test_time_conversion() -> None:
"""Test conversion of time units."""
assert timer.msec(1000) == timedelta(milliseconds=1000)
assert timer.to_msec(timedelta(milliseconds=1000)) == 1000

assert timer.msec_str(timedelta(milliseconds=1000)) == "1000"

assert timer.seconds(1) == timedelta(seconds=1)
assert timer.to_seconds(timedelta(seconds=1)) == 1

assert timer.sec_str(timedelta(seconds=1)) == "1"

assert timer.minutes(1) == timedelta(minutes=1)
assert timer.hours(1) == timedelta(hours=1)
assert timer.days(1) == timedelta(days=1)
assert timer.years(1) == timedelta(days=365)

assert timer.to_msec(timer.seconds(1)) == 1000
assert timer.to_seconds(timer.minutes(1)) == 60
assert timer.to_seconds(timer.hours(1)) == 60*60
assert timer.to_seconds(timer.days(1)) == 24*60*60
assert timer.to_seconds(timer.years(1)) == 365*24*60*60


def test_init() -> None:
"""Test Timer class init."""
t = timer.Timer()
assert t.duration == timedelta(0)
assert t.starting_time is not None

duration = timedelta(seconds=10)
t = timer.Timer(duration)
assert t.duration == duration
assert t.starting_time is not None

backdated_timestamp = datetime.now() - timedelta(seconds=10)
t = timer.Timer(backdated_timestamp=backdated_timestamp)
assert t.starting_time is not None
assert t.time_since_reset() >= timedelta(seconds=10)

def test_is_expired() -> None:
"""Test timer expiration."""
t = timer.Timer(timedelta(seconds=10))
assert not t.is_expired()

t = timer.Timer(timedelta(seconds=0))
assert t.is_expired()

t = timer.Timer(timedelta(seconds=10))
t.reset()
t.starting_time -= 10
assert t.is_expired()

def test_reset() -> None:
"""Test timer reset."""
t = timer.Timer(timedelta(seconds=10))
t.reset()
assert t.starting_time is not None
assert timer.sec_str(t.time_since_reset()) == timer.sec_str(timedelta(0))

def test_time() -> None:
"""Test time measurement, expiration, and time until expiration."""
t = timer.Timer(timedelta(seconds=10))
t.starting_time -= 5
assert timer.sec_str(t.time_since_reset()) == timer.sec_str(timedelta(seconds=5))

t = timer.Timer(timedelta(seconds=10))
t.starting_time -= 5
assert timer.sec_str(t.time_until_expiration()) == timer.sec_str(timedelta(seconds=5))

t = timer.Timer(timedelta(seconds=10))
t.starting_time -= 15 # Simulate time passing
assert t.time_until_expiration() == timedelta(0)

t = timer.Timer(timedelta(seconds=10))
t.starting_time -= 15
assert t.time_until_expiration() == timedelta(0)

t = timer.Timer(timedelta(seconds=10))
t.starting_time -= 5
assert timer.sec_str(t.time_until_expiration()) == timer.sec_str(timedelta(seconds=5))

def test_starting_timestamp() -> None:
"""Test timestamp conversion and integration."""
t = timer.Timer(timedelta(seconds=10))
timestamp_format = "%Y-%m-%d %H:%M:%S"
expected_timestamp = (datetime.now() - t.time_since_reset()).strftime(timestamp_format)
assert t.starting_timestamp(timestamp_format) == expected_timestamp
Loading