Skip to content

Commit

Permalink
fix(backend/attendances): round from_time and to_time to nearest 15min (
Browse files Browse the repository at this point in the history
#445)

refactor(backend): move rounding into `timed.utils`
chore(backend): added tests for rounding in `timed.utils`
  • Loading branch information
c0rydoras authored Sep 3, 2024
1 parent 1845c26 commit 9092dde
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 6 deletions.
55 changes: 55 additions & 0 deletions backend/timed/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from datetime import time, timedelta

import pytest

from timed.utils import round_time, round_timedelta


@pytest.mark.parametrize(
("unrounded", "rounded"),
[
(
time(5, 14, 11),
time(5, 15, 0),
),
(
time(12, 15, 0),
time(12, 15, 0),
),
(
time(14, 37, 20),
time(14, 30, 0),
),
(
time(22, 37, 35),
time(22, 45, 0),
),
(
time(23, 56, 35),
time(0, 0, 0),
),
],
)
def test_round_time(unrounded: time, rounded: time) -> None:
assert round_time(unrounded) == rounded


@pytest.mark.parametrize(
("unrounded", "rounded"),
[
(
timedelta(hours=5, minutes=17),
timedelta(hours=5, minutes=15),
),
(
timedelta(hours=2, minutes=37, seconds=25),
timedelta(hours=2, minutes=30),
),
(
timedelta(hours=3, minutes=37, seconds=48),
timedelta(hours=3, minutes=45),
),
],
)
def test_round_timedelta(unrounded: timedelta, rounded: timedelta) -> None:
assert round_timedelta(unrounded) == rounded
6 changes: 3 additions & 3 deletions backend/timed/tracking/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from django.contrib.postgres.search import SearchVector
from django.db import models

from timed.utils import round_timedelta

if TYPE_CHECKING:
from timed.employment.models import Employment

Expand Down Expand Up @@ -119,9 +121,7 @@ def save(self, *args, **kwargs) -> None: # noqa: ANN002,ANN003
This rounds the duration of the report to the nearest 15 minutes.
However, the duration must at least be 15 minutes long.
"""
self.duration = timedelta(
seconds=max(15 * 60, round(self.duration.seconds / (15 * 60)) * (15 * 60))
)
self.duration = round_timedelta(self.duration)

super().save(*args, **kwargs)

Expand Down
7 changes: 4 additions & 3 deletions backend/timed/tracking/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from timed.projects.models import Customer, Project, Task
from timed.serializers import TotalTimeRootMetaMixin
from timed.tracking import models
from timed.utils import round_time

if TYPE_CHECKING:
from typing import ClassVar
Expand All @@ -48,7 +49,7 @@ def validate(self, data):
instance = self.instance
from_time = data.get("from_time", instance and instance.from_time)
to_time = data.get("to_time", instance and instance.to_time)
user = instance and instance.user or data["user"]
user = (instance and instance.user) or data["user"]

def validate_running_activity():
if activity.filter(to_time__isnull=True).exists():
Expand Down Expand Up @@ -105,8 +106,8 @@ def validate(self, data):
Ensure that attendances end after they start.
"""
instance = self.instance
from_time = data.get("from_time", instance and instance.from_time)
to_time = data.get("to_time", instance and instance.to_time)
from_time = round_time(data.get("from_time", instance and instance.from_time))
to_time = round_time(data.get("to_time", instance and instance.to_time))

if to_time == from_time:
raise ValidationError(
Expand Down
25 changes: 25 additions & 0 deletions backend/timed/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from datetime import time, timedelta


def round_time(t: time) -> time:
"""Round a datetime.time to the nearest 15min."""
minutes = round((t.minute * 60 + t.second) / (60 * 15)) * 15
hours = t.hour

if minutes > time.max.minute:
hours += 1
minutes = 0

if hours > time.max.hour:
hours = 0

return time(
hours,
minutes,
0,
)


def round_timedelta(td: timedelta) -> timedelta:
"""Round a datetime.timedelta to the nearest 15min."""
return timedelta(seconds=max(15 * 60, round(td.seconds / (15 * 60)) * 15 * 60))

0 comments on commit 9092dde

Please sign in to comment.